feat(compiler-core): create transform for v-model (#146)
This commit is contained in:
		
							parent
							
								
									99bdc5a8c8
								
							
						
					
					
						commit
						87c3d2edae
					
				@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
 | 
				
			||||||
 | 
					"import { createVNode, createBlock, openBlock } from \\"vue\\"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function render() {
 | 
				
			||||||
 | 
					  const _ctx = this
 | 
				
			||||||
 | 
					  return (openBlock(), createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					    modelValue: _ctx.model[_ctx.index],
 | 
				
			||||||
 | 
					    \\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
 | 
				
			||||||
 | 
					  }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model compound expression 1`] = `
 | 
				
			||||||
 | 
					"const _Vue = Vue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return function render() {
 | 
				
			||||||
 | 
					  with (this) {
 | 
				
			||||||
 | 
					    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return (_openBlock(), _createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					      modelValue: model[index],
 | 
				
			||||||
 | 
					      \\"onUpdate:modelValue\\": $event => (model[index] = $event)
 | 
				
			||||||
 | 
					    }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model simple exprssion (with prefixIdentifiers) 1`] = `
 | 
				
			||||||
 | 
					"import { createVNode, createBlock, openBlock } from \\"vue\\"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function render() {
 | 
				
			||||||
 | 
					  const _ctx = this
 | 
				
			||||||
 | 
					  return (openBlock(), createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					    modelValue: _ctx.model,
 | 
				
			||||||
 | 
					    \\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
 | 
				
			||||||
 | 
					  }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model simple exprssion 1`] = `
 | 
				
			||||||
 | 
					"const _Vue = Vue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return function render() {
 | 
				
			||||||
 | 
					  with (this) {
 | 
				
			||||||
 | 
					    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return (_openBlock(), _createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					      modelValue: model,
 | 
				
			||||||
 | 
					      \\"onUpdate:modelValue\\": $event => (model = $event)
 | 
				
			||||||
 | 
					    }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model with argument 1`] = `
 | 
				
			||||||
 | 
					"const _Vue = Vue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return function render() {
 | 
				
			||||||
 | 
					  with (this) {
 | 
				
			||||||
 | 
					    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return (_openBlock(), _createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					      value: model,
 | 
				
			||||||
 | 
					      \\"onUpdate:value\\": $event => (model = $event)
 | 
				
			||||||
 | 
					    }, null, 8 /* PROPS */, [\\"value\\", \\"onUpdate:value\\"]))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
 | 
				
			||||||
 | 
					"import { createVNode, createBlock, openBlock } from \\"vue\\"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function render() {
 | 
				
			||||||
 | 
					  const _ctx = this
 | 
				
			||||||
 | 
					  return (openBlock(), createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					    [_ctx.value]: _ctx.model,
 | 
				
			||||||
 | 
					    [\\"onUpdate:\\"+_ctx.value]: $event => (_ctx.model = $event)
 | 
				
			||||||
 | 
					  }, null, 16 /* FULL_PROPS */))
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`compiler: transform v-model with dynamic argument 1`] = `
 | 
				
			||||||
 | 
					"const _Vue = Vue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return function render() {
 | 
				
			||||||
 | 
					  with (this) {
 | 
				
			||||||
 | 
					    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return (_openBlock(), _createBlock(\\"input\\", {
 | 
				
			||||||
 | 
					      [value]: model,
 | 
				
			||||||
 | 
					      [\\"onUpdate:\\"+value]: $event => (model = $event)
 | 
				
			||||||
 | 
					    }, null, 16 /* FULL_PROPS */))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}"
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
							
								
								
									
										355
									
								
								packages/compiler-core/__tests__/transforms/vModel.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								packages/compiler-core/__tests__/transforms/vModel.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,355 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  parse,
 | 
				
			||||||
 | 
					  transform,
 | 
				
			||||||
 | 
					  generate,
 | 
				
			||||||
 | 
					  ElementNode,
 | 
				
			||||||
 | 
					  ObjectExpression,
 | 
				
			||||||
 | 
					  CompilerOptions,
 | 
				
			||||||
 | 
					  CallExpression
 | 
				
			||||||
 | 
					} from '../../src'
 | 
				
			||||||
 | 
					import { ErrorCodes } from '../../src/errors'
 | 
				
			||||||
 | 
					import { transformModel } from '../../src/transforms/vModel'
 | 
				
			||||||
 | 
					import { transformElement } from '../../src/transforms/transformElement'
 | 
				
			||||||
 | 
					import { transformExpression } from '../../src/transforms/transformExpression'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function parseWithVModel(template: string, options: CompilerOptions = {}) {
 | 
				
			||||||
 | 
					  const ast = parse(template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  transform(ast, {
 | 
				
			||||||
 | 
					    nodeTransforms: [transformExpression, transformElement],
 | 
				
			||||||
 | 
					    directiveTransforms: {
 | 
				
			||||||
 | 
					      ...options.directiveTransforms,
 | 
				
			||||||
 | 
					      model: transformModel
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    ...options
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return ast
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('compiler: transform v-model', () => {
 | 
				
			||||||
 | 
					  test('simple exprssion', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model="model" />')
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: 'model',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'onUpdate:modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('simple exprssion (with prefixIdentifiers)', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model="model" />', {
 | 
				
			||||||
 | 
					      prefixIdentifiers: true
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: '_ctx.model',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'onUpdate:modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('compound expression', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model="model[index]" />')
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: 'model[index]',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'onUpdate:modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'model[index]',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('compound expression (with prefixIdentifiers)', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model="model[index]" />', {
 | 
				
			||||||
 | 
					      prefixIdentifiers: true
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          '[',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.index',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ']'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'onUpdate:modelValue',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          '[',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.index',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ']',
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('with argument', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model:value="model" />')
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'value',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: 'model',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'onUpdate:value',
 | 
				
			||||||
 | 
					        isStatic: true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('with dynamic argument', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model:[value]="model" />')
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: 'value',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: 'model',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'onUpdate:',
 | 
				
			||||||
 | 
					            isStatic: true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          '+',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'value',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('with dynamic argument (with prefixIdentifiers)', () => {
 | 
				
			||||||
 | 
					    const root = parseWithVModel('<input v-model:[value]="model" />', {
 | 
				
			||||||
 | 
					      prefixIdentifiers: true
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const node = root.children[0] as ElementNode
 | 
				
			||||||
 | 
					    const props = ((node.codegenNode as CallExpression)
 | 
				
			||||||
 | 
					      .arguments[1] as ObjectExpression).properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[0]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        content: '_ctx.value',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        content: '_ctx.model',
 | 
				
			||||||
 | 
					        isStatic: false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(props[1]).toMatchObject({
 | 
				
			||||||
 | 
					      key: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: 'onUpdate:',
 | 
				
			||||||
 | 
					            isStatic: true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          '+',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.value',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      value: {
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          '$event => (',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            content: '_ctx.model',
 | 
				
			||||||
 | 
					            isStatic: false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          ' = $event)'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('errors', () => {
 | 
				
			||||||
 | 
					    test('missing expression', () => {
 | 
				
			||||||
 | 
					      const onError = jest.fn()
 | 
				
			||||||
 | 
					      parseWithVModel('<span v-model />', { onError })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(onError).toHaveBeenCalledTimes(1)
 | 
				
			||||||
 | 
					      expect(onError).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          code: ErrorCodes.X_V_MODEL_NO_EXPRESSION
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('empty expression', () => {
 | 
				
			||||||
 | 
					      const onError = jest.fn()
 | 
				
			||||||
 | 
					      parseWithVModel('<span v-model="" />', { onError })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(onError).toHaveBeenCalledTimes(1)
 | 
				
			||||||
 | 
					      expect(onError).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					        expect.objectContaining({
 | 
				
			||||||
 | 
					          code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@ -1,5 +1,9 @@
 | 
				
			|||||||
import { Position } from '../src/ast'
 | 
					import { Position, NodeTypes } from '../src/ast'
 | 
				
			||||||
import { getInnerRange, advancePositionWithClone } from '../src/utils'
 | 
					import {
 | 
				
			||||||
 | 
					  getInnerRange,
 | 
				
			||||||
 | 
					  advancePositionWithClone,
 | 
				
			||||||
 | 
					  isEmptyExpression
 | 
				
			||||||
 | 
					} from '../src/utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function p(line: number, column: number, offset: number): Position {
 | 
					function p(line: number, column: number, offset: number): Position {
 | 
				
			||||||
  return { column, line, offset }
 | 
					  return { column, line, offset }
 | 
				
			||||||
@ -67,3 +71,38 @@ describe('getInnerRange', () => {
 | 
				
			|||||||
    expect(loc2.end.offset).toBe(7)
 | 
					    expect(loc2.end.offset).toBe(7)
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('isEmptyExpression', () => {
 | 
				
			||||||
 | 
					  test('empty', () => {
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      isEmptyExpression({
 | 
				
			||||||
 | 
					        content: '',
 | 
				
			||||||
 | 
					        type: NodeTypes.SIMPLE_EXPRESSION,
 | 
				
			||||||
 | 
					        isStatic: true,
 | 
				
			||||||
 | 
					        loc: null as any
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).toBe(true)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('spaces', () => {
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      isEmptyExpression({
 | 
				
			||||||
 | 
					        content: '  \t  ',
 | 
				
			||||||
 | 
					        type: NodeTypes.SIMPLE_EXPRESSION,
 | 
				
			||||||
 | 
					        isStatic: true,
 | 
				
			||||||
 | 
					        loc: null as any
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).toBe(true)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('identifier', () => {
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      isEmptyExpression({
 | 
				
			||||||
 | 
					        content: 'foo',
 | 
				
			||||||
 | 
					        type: NodeTypes.SIMPLE_EXPRESSION,
 | 
				
			||||||
 | 
					        isStatic: true,
 | 
				
			||||||
 | 
					        loc: null as any
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).toBe(false)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -79,6 +79,8 @@ export const enum ErrorCodes {
 | 
				
			|||||||
  X_V_SLOT_DUPLICATE_SLOT_NAMES,
 | 
					  X_V_SLOT_DUPLICATE_SLOT_NAMES,
 | 
				
			||||||
  X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
 | 
					  X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
 | 
				
			||||||
  X_V_SLOT_MISPLACED,
 | 
					  X_V_SLOT_MISPLACED,
 | 
				
			||||||
 | 
					  X_V_MODEL_NO_EXPRESSION,
 | 
				
			||||||
 | 
					  X_V_MODEL_MALFORMED_EXPRESSION,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // generic errors
 | 
					  // generic errors
 | 
				
			||||||
  X_PREFIX_ID_NOT_SUPPORTED,
 | 
					  X_PREFIX_ID_NOT_SUPPORTED,
 | 
				
			||||||
@ -167,6 +169,8 @@ export const errorMessages: { [code: number]: string } = {
 | 
				
			|||||||
    `Extraneous children found when component has explicit slots. ` +
 | 
					    `Extraneous children found when component has explicit slots. ` +
 | 
				
			||||||
    `These children will be ignored.`,
 | 
					    `These children will be ignored.`,
 | 
				
			||||||
  [ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or <template> tags.`,
 | 
					  [ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or <template> tags.`,
 | 
				
			||||||
 | 
					  [ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
 | 
				
			||||||
 | 
					  [ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model has invalid expression.`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // generic errors
 | 
					  // generic errors
 | 
				
			||||||
  [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
 | 
					  [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1,54 @@
 | 
				
			|||||||
// TODO
 | 
					import { DirectiveTransform } from '../transform'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  createSimpleExpression,
 | 
				
			||||||
 | 
					  createObjectProperty,
 | 
				
			||||||
 | 
					  createCompoundExpression,
 | 
				
			||||||
 | 
					  NodeTypes,
 | 
				
			||||||
 | 
					  Property
 | 
				
			||||||
 | 
					} from '../ast'
 | 
				
			||||||
 | 
					import { createCompilerError, ErrorCodes } from '../errors'
 | 
				
			||||||
 | 
					import { isEmptyExpression } from '../utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const transformModel: DirectiveTransform = (dir, node, context) => {
 | 
				
			||||||
 | 
					  const { exp, arg } = dir
 | 
				
			||||||
 | 
					  if (!exp) {
 | 
				
			||||||
 | 
					    context.onError(createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return createTransformProps()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isEmptyExpression(exp)) {
 | 
				
			||||||
 | 
					    context.onError(
 | 
				
			||||||
 | 
					      createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return createTransformProps()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const propName = arg ? arg : createSimpleExpression('modelValue', true)
 | 
				
			||||||
 | 
					  const eventName = arg
 | 
				
			||||||
 | 
					    ? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
 | 
				
			||||||
 | 
					      ? createSimpleExpression('onUpdate:' + arg.content, true)
 | 
				
			||||||
 | 
					      : createCompoundExpression([
 | 
				
			||||||
 | 
					          createSimpleExpression('onUpdate:', true),
 | 
				
			||||||
 | 
					          '+',
 | 
				
			||||||
 | 
					          ...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					    : createSimpleExpression('onUpdate:modelValue', true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return createTransformProps([
 | 
				
			||||||
 | 
					    createObjectProperty(propName, dir.exp!),
 | 
				
			||||||
 | 
					    createObjectProperty(
 | 
				
			||||||
 | 
					      eventName,
 | 
				
			||||||
 | 
					      createCompoundExpression([
 | 
				
			||||||
 | 
					        `$event => (`,
 | 
				
			||||||
 | 
					        ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
 | 
				
			||||||
 | 
					        ` = $event)`
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  ])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createTransformProps(props: Property[] = []) {
 | 
				
			||||||
 | 
					  return { props, needRuntime: false }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,8 @@ import {
 | 
				
			|||||||
  BlockCodegenNode,
 | 
					  BlockCodegenNode,
 | 
				
			||||||
  ElementCodegenNode,
 | 
					  ElementCodegenNode,
 | 
				
			||||||
  SlotOutletCodegenNode,
 | 
					  SlotOutletCodegenNode,
 | 
				
			||||||
  ComponentCodegenNode
 | 
					  ComponentCodegenNode,
 | 
				
			||||||
 | 
					  ExpressionNode
 | 
				
			||||||
} from './ast'
 | 
					} from './ast'
 | 
				
			||||||
import { parse } from 'acorn'
 | 
					import { parse } from 'acorn'
 | 
				
			||||||
import { walk } from 'estree-walker'
 | 
					import { walk } from 'estree-walker'
 | 
				
			||||||
@ -237,3 +238,7 @@ export function toValidAssetId(
 | 
				
			|||||||
): string {
 | 
					): string {
 | 
				
			||||||
  return `_${type}_${name.replace(/[^\w]/g, '')}`
 | 
					  return `_${type}_${name.replace(/[^\w]/g, '')}`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isEmptyExpression(node: ExpressionNode) {
 | 
				
			||||||
 | 
					  return node.type === NodeTypes.SIMPLE_EXPRESSION && !node.content.trim()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user