Repository: vuejs/vue
Pull request: add sync modifier with bind object

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [ ] Bugfix
- [x] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**
#5937 

Changed files:
filename: flow/component.js
patch: @@ -120,7 +120,7 @@ declare interface Component {
   // renderSlot
   _t: (name: string, fallback: ?Array<VNode>, props: ?Object) => ?Array<VNode>;
   // apply v-bind object
-  _b: (data: any, value: any, asProp?: boolean) => VNodeData;
+  _b: (data: any, tag: string, value: any, asProp: boolean, isSync?: boolean) => VNodeData;
   // check custom keyCode
   _k: (eventKeyCode: number, key: string, builtInAlias: number | Array<number> | void) => boolean;
   // resolve scoped slots

filename: src/compiler/directives/bind.js
patch: @@ -2,8 +2,10 @@
 
 export default function bind (el: ASTElement, dir: ASTDirective) {
   el.wrapData = (code: string) => {
-    return `_b(${code},'${el.tag}',${dir.value}${
-      dir.modifiers && dir.modifiers.prop ? ',true' : ''
+    return `_b(${code},'${el.tag}',${dir.value},${
+      dir.modifiers && dir.modifiers.prop ? 'true' : 'false'
+    }${
+      dir.modifiers && dir.modifiers.sync ? ',true' : ''
     })`
   }
 }

filename: src/core/instance/render-helpers/bind-object-props.js
patch: @@ -16,7 +16,8 @@ export function bindObjectProps (
   data: any,
   tag: string,
   value: any,
-  asProp?: boolean
+  asProp: boolean,
+  isSync?: boolean
 ): VNodeData {
   if (value) {
     if (!isObject(value)) {
@@ -44,6 +45,13 @@ export function bindObjectProps (
         }
         if (!(key in hash)) {
           hash[key] = value[key]
+
+          if (isSync) {
+            const on = data.on || (data.on = {})
+            on[`update:${key}`] = function ($event) {
+              value[key] = $event
+            }
+          }
         }
       }
     }

filename: test/unit/features/directives/bind.spec.js
patch: @@ -187,6 +187,36 @@ describe('Directive v-bind', () => {
     }).then(done)
   })
 
+  it('.sync modifier with bind object', done => {
+    const vm = new Vue({
+      template: `<test v-bind.sync="test"/>`,
+      data: {
+        test: {
+          fooBar: 1
+        }
+      },
+      components: {
+        test: {
+          props: ['fooBar'],
+          template: `<div @click="handleUpdate">{{ fooBar }}</div>`,
+          methods: {
+            handleUpdate () {
+              this.$emit('update:fooBar', 2)
+            }
+          }
+        }
+      }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('1')
+    triggerEvent(vm.$el, 'click')
+    waitForUpdate(() => {
+      expect(vm.$el.textContent).toBe('2')
+      vm.test.fooBar = 3
+    }).then(() => {
+      expect(vm.$el.textContent).toBe('3')
+    }).then(done)
+  })
+
   it('bind object with overwrite', done => {
     const vm = new Vue({
       template: '<input v-bind="test" id="foo" :class="test.value">',

filename: test/unit/modules/compiler/codegen.spec.js
patch: @@ -126,7 +126,21 @@ describe('codegen', () => {
   it('generate v-bind directive', () => {
     assertCodegen(
       '<p v-bind="test"></p>',
-      `with(this){return _c('p',_b({},'p',test))}`
+      `with(this){return _c('p',_b({},'p',test,false))}`
+    )
+  })
+
+  it('generate v-bind with prop directive', () => {
+    assertCodegen(
+      '<p v-bind.prop="test"></p>',
+      `with(this){return _c('p',_b({},'p',test,true))}`
+    )
+  })
+
+  it('generate v-bind directive with sync modifier', () => {
+    assertCodegen(
+      '<p v-bind.sync="test"></p>',
+      `with(this){return _c('p',_b({},'p',test,false,true))}`
     )
   })