Repository: vuejs/vue
Pull request: Fix terminal directive compilation loop

Related #2272


Changed files:
filename: src/compiler/compile.js
patch: @@ -586,15 +586,17 @@ function checkTerminalDirectives (el, attrs, options) {
     }
   }
 
-  var attr, name, value, matched, dirName, arg, def, termDef
+  var attr, name, value, modifiers, matched, dirName, rawName, arg, def, termDef
   for (var i = 0, j = attrs.length; i < j; i++) {
     attr = attrs[i]
-    if ((matched = attr.name.match(dirAttrRE))) {
+    modifiers = parseModifiers(attr.name)
+    name = attr.name.replace(modifierRE, '')
+    if ((matched = name.match(dirAttrRE))) {
       def = resolveAsset(options, 'directives', matched[1])
       if (def && def.terminal) {
         if (!termDef || ((def.priority || DEFAULT_TERMINAL_PRIORITY) > termDef.priority)) {
           termDef = def
-          name = attr.name
+          rawName = attr.name
           value = attr.value
           dirName = matched[1]
           arg = matched[2]
@@ -604,7 +606,7 @@ function checkTerminalDirectives (el, attrs, options) {
   }
 
   if (termDef) {
-    return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, name, arg)
+    return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers)
   }
 }
 
@@ -622,28 +624,24 @@ skip.terminal = true
  * @param {String} value
  * @param {Object} options
  * @param {Object} def
- * @param {String} [attrName]
+ * @param {String} [rawName]
  * @param {String} [arg]
+ * @param {Object} [modifiers]
  * @return {Function} terminalLinkFn
  */
 
-function makeTerminalNodeLinkFn (el, dirName, value, options, def, attrName, arg) {
+function makeTerminalNodeLinkFn (el, dirName, value, options, def, rawName, arg, modifiers) {
   var parsed = parseDirective(value)
   var descriptor = {
     name: dirName,
+    arg: arg,
     expression: parsed.expression,
     filters: parsed.filters,
     raw: value,
-    rawName: attrName,
+    attr: rawName,
+    modifiers: modifiers,
     def: def
   }
-  if (attrName) {
-    descriptor.rawName = attrName
-    descriptor.modifiers = parseModifiers(attrName)
-  }
-  if (arg) {
-    descriptor.arg = arg.replace(modifierRE, '')
-  }
   // check ref for v-for and router-view
   if (dirName === 'for' || dirName === 'router-view') {
     descriptor.ref = findRef(el)

filename: test/unit/specs/compiler/compile_spec.js
patch: @@ -1,5 +1,6 @@
 var Vue = require('src')
 var _ = require('src/util')
+var FragmentFactory = require('src/fragment/factory')
 var compiler = require('src/compiler')
 var compile = compiler.compile
 var publicDirectives = require('src/directives/public')
@@ -264,7 +265,7 @@ describe('Compile', function () {
     var args = vm._bindDir.calls.argsFor(0)
     expect(args[0].name).toBe('term')
     expect(args[0].expression).toBe('foo')
-    expect(args[0].rawName).toBe('v-term:arg1.modifier1.modifier2')
+    expect(args[0].attr).toBe('v-term:arg1.modifier1.modifier2')
     expect(args[0].arg).toBe('arg1')
     expect(args[0].modifiers.modifier1).toBe(true)
     expect(args[0].modifiers.modifier2).toBe(true)
@@ -286,7 +287,7 @@ describe('Compile', function () {
     var args = vm._bindDir.calls.argsFor(0)
     expect(args[0].name).toBe('term')
     expect(args[0].expression).toBe('')
-    expect(args[0].rawName).toBe('v-term:arg1')
+    expect(args[0].attr).toBe('v-term:arg1')
     expect(args[0].arg).toBe('arg1')
     expect(args[0].def).toBe(defTerminal)
   })
@@ -669,4 +670,48 @@ describe('Compile', function () {
     expect(el.textContent).toBe('worked!')
     expect(getWarnCount()).toBe(0)
   })
+
+  // #xxx
+  it('should compile build-in terminal directive wihtout loop', function (done) {
+    var vm = new Vue({
+      el: el,
+      data: { show: false },
+      template: '<p v-if:arg1.modifier1="show">hello world</p>'
+    })
+    vm.show = true
+    _.nextTick(function () {
+      expect(el.textContent).toBe('hello world')
+      done()
+    })
+  })
+
+  it('should compile custom terminal directive wihtout loop', function (done) {
+    var vm = new Vue({
+      el: el,
+      data: { show: false },
+      template: '<p v-if="show" v-inject:modal.modifier1="foo">hello world</p>',
+      directives: {
+        inject: {
+          terminal: true,
+          priority: Vue.options.directives.if.priority + 1,
+          bind: function () {
+            this.anchor = _.createAnchor('v-inject')
+            _.replace(this.el, this.anchor)
+            var factory = new FragmentFactory(this.vm, this.el)
+            this.frag = factory.create(this._host, this._scope, this._frag)
+            this.frag.before(this.anchor)
+          },
+          unbind: function () {
+            this.frag.remove()
+            _.replace(this.anchor, this.el)
+          }
+        }
+      }
+    })
+    vm.show = true
+    _.nextTick(function () {
+      expect(el.textContent).toBe('hello world')
+      done()
+    })
+  })
 })