Repository: vuejs/eslint-plugin-vue
Pull request #582 (2018-09-23 08:23:23)

Update README on integration with Atom editor


Patch:
diff --git a/README.md b/README.md
index 2cebaac5..1592d02a 100644
--- a/README.md
+++ b/README.md
@@ -287,6 +287,7 @@ The `vue-eslint-parser` uses the parser which is set by `parserOptions.parser` t
   - CLI targets only `.js` files by default. You have to specify additional extensions by `--ext` option or glob patterns. E.g. `eslint "src/**/*.{js,vue}"` or `eslint src --ext .vue`. If you use `@vue/cli-plugin-eslint` and the `vue-cli-service lint` command - you don't have to worry about it.
   - VSCode targets only JavaScript or HTML files by default. You have to add `"vue"` to the `"eslint.validate"` array in vscode settings. e.g. `"eslint.validate": [ "javascript", "javascriptreact", "vue" ]`
   - If you use `Vetur` plugin in VSCode - set `"vetur.validation.template": false` to avoid default Vetur template validation. Check out [vetur documentation](https://github.com/vuejs/vetur/blob/master/docs/linting-error.md) for more info.
+  - For Atom editor, you need to go into Settings -> Packages -> linter-eslint, under the option “List of scopes to run eslint on”, add `text.html.vue`.
 
 ## :anchor: Semantic Versioning Policy
 
diff --git a/docs/rules/require-valid-default-prop.md b/docs/rules/require-valid-default-prop.md
index 2049534b..eee9692c 100644
--- a/docs/rules/require-valid-default-prop.md
+++ b/docs/rules/require-valid-default-prop.md
@@ -49,10 +49,16 @@ props: {
   // object/array defaults should be returned from a factory function
   propE: {
     type: Object,
-    default: function () {
+    default() {
       return { message: 'hello' }
     }
-  }
+  },
+  propF: {
+    type: Array,
+    default() {
+      return []
+    }
+  }  
 }
 ```
 
diff --git a/docs/rules/valid-v-on.md b/docs/rules/valid-v-on.md
index 18580ccc..02bc8ba9 100644
--- a/docs/rules/valid-v-on.md
+++ b/docs/rules/valid-v-on.md
@@ -36,7 +36,24 @@ This rule does not check syntax errors in directives because it's checked by [no
 
 ## :wrench: Options
 
-Nothing.
+This rule has an object option:
+
+`"modifiers"`: [] (default) array of additional allowed modifiers.
+
+### Example:
+
+```json
+"vue/valid-v-on": [2, {
+  modifiers: ['foo']
+}]
+```
+
+:+1: Examples of **correct** code for this rule:
+
+```html
+<div @click.foo="foo"/>
+<div v-on:click.foo="foo"/>
+```
 
 ## :couple: Related rules
 
diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js
index 52e88b2e..61d82503 100644
--- a/lib/rules/require-prop-type-constructor.js
+++ b/lib/rules/require-prop-type-constructor.js
@@ -19,7 +19,7 @@ const forbiddenTypes = [
   'UpdateExpression'
 ]
 
-const isForbiddenType = nodeType => forbiddenTypes.indexOf(nodeType) > -1
+const isForbiddenType = node => forbiddenTypes.indexOf(node.type) > -1 && node.raw !== 'null'
 
 module.exports = {
   meta: {
@@ -46,7 +46,7 @@ module.exports = {
     }
 
     const checkPropertyNode = (p) => {
-      if (isForbiddenType(p.value.type)) {
+      if (isForbiddenType(p.value)) {
         context.report({
           node: p.value,
           message,
@@ -57,7 +57,7 @@ module.exports = {
         })
       } else if (p.value.type === 'ArrayExpression') {
         p.value.elements
-          .filter(prop => isForbiddenType(prop.type))
+          .filter(prop => isForbiddenType(prop))
           .forEach(prop => context.report({
             node: prop,
             message,
@@ -79,20 +79,21 @@ module.exports = {
 
       if (!node) return
 
-      node.value.properties.forEach(p => {
-        if (isForbiddenType(p.value.type) || p.value.type === 'ArrayExpression') {
-          checkPropertyNode(p)
-        } else if (p.value.type === 'ObjectExpression') {
-          const typeProperty = p.value.properties.find(prop =>
-            prop.type === 'Property' &&
-            prop.key.name === 'type'
-          )
+      node.value.properties
+        .forEach(p => {
+          if (isForbiddenType(p.value) || p.value.type === 'ArrayExpression') {
+            checkPropertyNode(p)
+          } else if (p.value.type === 'ObjectExpression') {
+            const typeProperty = p.value.properties.find(prop =>
+              prop.type === 'Property' &&
+              prop.key.name === 'type'
+            )
 
-          if (!typeProperty) return
+            if (!typeProperty) return
 
-          checkPropertyNode(typeProperty)
-        }
-      })
+            checkPropertyNode(typeProperty)
+          }
+        })
     })
   }
 }
diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js
index 203a2ae3..13073d9a 100644
--- a/lib/rules/valid-v-on.js
+++ b/lib/rules/valid-v-on.js
@@ -93,7 +93,7 @@ const KEY_ALIASES = new Set([
   'media-previous-track', 'power', 'unidentified'
 ])
 
-function isValidModifier (modifier) {
+function isValidModifier (modifier, customModifiers) {
   return (
     // built-in aliases
     VALID_MODIFIERS.has(modifier) ||
@@ -102,7 +102,9 @@ function isValidModifier (modifier) {
     // keyAlias (an Unicode character)
     Array.from(modifier).length === 1 ||
     // keyAlias (special keys)
-    KEY_ALIASES.has(modifier)
+    KEY_ALIASES.has(modifier) ||
+    // custom modifiers
+    customModifiers.has(modifier)
   )
 }
 
@@ -118,14 +120,27 @@ module.exports = {
       url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-on.md'
     },
     fixable: null,
-    schema: []
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          modifiers: {
+            type: 'array'
+          }
+        },
+        additionalProperties: false
+      }
+    ]
   },
 
   create (context) {
+    const options = context.options[0] || {}
+    const customModifiers = new Set(options.modifiers || [])
+
     return utils.defineTemplateBodyVisitor(context, {
       "VAttribute[directive=true][key.name='on']" (node) {
         for (const modifier of node.key.modifiers) {
-          if (!isValidModifier(modifier)) {
+          if (!isValidModifier(modifier, customModifiers)) {
             context.report({
               node,
               loc: node.loc,
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index a4dee6f2..c8c86725 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -18,7 +18,6 @@ const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionEx
 const LT_CHAR = /[\r\n\u2028\u2029]/
 const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
 const BLOCK_COMMENT_PREFIX = /^\s*\*/
-const TRIVIAL_PUNCTUATOR = /^[(){}[\],;]$/
 
 /**
  * Normalize options.
@@ -245,21 +244,6 @@ function isClosingToken (token) {
   )
 }
 
-/**
- * Check whether a given token is trivial or not.
- * @param {Token} token The token to check.
- * @returns {boolean} `true` if the token is trivial.
- */
-function isTrivialToken (token) {
-  return token != null && (
-    (token.type === 'Punctuator' && TRIVIAL_PUNCTUATOR.test(token.value)) ||
-    token.type === 'HTMLTagOpen' ||
-    token.type === 'HTMLEndTagOpen' ||
-    token.type === 'HTMLTagClose' ||
-    token.type === 'HTMLSelfClosingTagClose'
-  )
-}
-
 /**
  * Creates AST event handlers for html-indent.
  *
@@ -564,24 +548,22 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
   /**
    * Calculate correct indentation of the line of the given tokens.
    * @param {Token[]} tokens Tokens which are on the same line.
-   * @returns {number} Correct indentation. If it failed to calculate then `Number.MAX_SAFE_INTEGER`.
+   * @returns {object|null} Correct indentation. If it failed to calculate then `null`.
    */
-  function getExpectedIndent (tokens) {
-    const trivial = isTrivialToken(tokens[0])
-    let expectedIndent = Number.MAX_SAFE_INTEGER
+  function getExpectedIndents (tokens) {
+    const expectedIndents = []
 
     for (let i = 0; i < tokens.length; ++i) {
       const token = tokens[i]
       const offsetInfo = offsets.get(token)
 
-      // If the first token is not trivial then ignore trivial following tokens.
-      if (offsetInfo != null && (trivial || !isTrivialToken(token))) {
+      if (offsetInfo != null) {
         if (offsetInfo.expectedIndent != null) {
-          expectedIndent = Math.min(expectedIndent, offsetInfo.expectedIndent)
+          expectedIndents.push(offsetInfo.expectedIndent)
         } else {
           const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
           if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) {
-            expectedIndent = Math.min(expectedIndent, baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize)
+            expectedIndents.push(baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize)
             if (baseOffsetInfo.baseline) {
               break
             }
@@ -589,8 +571,14 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         }
       }
     }
+    if (!expectedIndents.length) {
+      return null
+    }
 
-    return expectedIndent
+    return {
+      expectedIndent: expectedIndents[0],
+      expectedBaseIndent: expectedIndents.reduce((a, b) => Math.min(a, b))
+    }
   }
 
   /**
@@ -746,11 +734,14 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     // Calculate and save expected indentation.
     const firstToken = tokens[0]
     const actualIndent = firstToken.loc.start.column
-    const expectedIndent = getExpectedIndent(tokens)
-    if (expectedIndent === Number.MAX_SAFE_INTEGER) {
+    const expectedIndents = getExpectedIndents(tokens)
+    if (!expectedIndents) {
       return
     }
 
+    const expectedBaseIndent = expectedIndents.expectedBaseIndent
+    const expectedIndent = expectedIndents.expectedIndent
+
     // Debug log
     // console.log('line', firstToken.loc.start.line, '=', { actualIndent, expectedIndent }, 'from:')
     // for (const token of tokens) {
@@ -773,11 +764,11 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         if (offsetInfo.baseline) {
           // This is a baseline token, so the expected indent is the column of this token.
           if (options.indentChar === ' ') {
-            offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedIndent - actualIndent)
+            offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedBaseIndent - actualIndent)
           } else {
             // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.
             // But the additional offset isn't needed if it's at the beginning of the line.
-            offsetInfo.expectedIndent = expectedIndent + (token === tokens[0] ? 0 : 1)
+            offsetInfo.expectedIndent = expectedBaseIndent + (token === tokens[0] ? 0 : 1)
           }
           baseline.add(token)
         } else if (baseline.has(offsetInfo.baseToken)) {
@@ -786,7 +777,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
           baseline.add(token)
         } else {
           // Otherwise, set the expected indent of this line.
-          offsetInfo.expectedIndent = expectedIndent
+          offsetInfo.expectedIndent = expectedBaseIndent
         }
       }
     }


Test patch:
diff --git a/tests/fixtures/script-indent/array-expression-03.vue b/tests/fixtures/script-indent/array-expression-03.vue
new file mode 100644
index 00000000..0e15d8aa
--- /dev/null
+++ b/tests/fixtures/script-indent/array-expression-03.vue
@@ -0,0 +1,6 @@
+<!--{}-->
+<script>
+var v = [() =>
+           1
+         ,b]
+</script>
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/array-expression-04.vue b/tests/fixtures/script-indent/array-expression-04.vue
new file mode 100644
index 00000000..1530c24b
--- /dev/null
+++ b/tests/fixtures/script-indent/array-expression-04.vue
@@ -0,0 +1,6 @@
+<!--{}-->
+<script>
+var v = [() =>
+  1
+,]
+</script>
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/indent-valid-fixture-01.vue b/tests/fixtures/script-indent/indent-valid-fixture-01.vue
new file mode 100644
index 00000000..c1423bf9
--- /dev/null
+++ b/tests/fixtures/script-indent/indent-valid-fixture-01.vue
@@ -0,0 +1,541 @@
+<!--{"options":[2,{"switchCase":1}]}-->
+<!-- see https://github.com/eslint/eslint/blob/master/tests/fixtures/rules/indent/indent-valid-fixture-1.js -->
+<script>
+/* ESLint setting reference value
+eslint indent:[2,2,{
+"SwitchCase":1,
+"VariableDeclarator":{"var":2,"let":2},
+"FunctionExpression":{"parameters":"first"},
+"CallExpression":{"arguments":"first"},
+}]*/
+if (a) {
+  var b = c;
+  var d = e
+    * f;
+  var e = f; // <-
+  // ->
+  function g() {
+    if (h) {
+      var i = j;
+    } // <-
+  } // <-
+
+  while (k) l++;
+  while (m) {
+    n--; // ->
+  } // <-
+
+  do {
+    o = p +
+      q;
+    o = p +
+      q;
+  } while(r); // <-
+
+  for (var s in t) {
+    u++;
+  }
+
+  for (;;) {
+    v++; // <-
+  }
+
+  if ( w ) {
+    x++;
+  } else if (y) {
+    z++; // <-
+    aa++;
+  } else { // <-
+    bb++; // ->
+  } // ->
+}
+
+/**/var b; // NO ERROR: single line multi-line comments followed by code is OK
+/*
+*
+*/ var b; // NO ERROR: multi-line comments followed by code is OK
+
+var arr = [
+  a,
+  b,
+  c,
+  function (){
+    d
+  }, // <-
+  {},
+  {
+    a: b,
+    c: d,
+    d: e
+  },
+  [
+    f,
+    g,
+    h,
+    i
+  ],
+  [j]
+];
+
+var obj = {
+  a: {
+    b: {
+      c: d,
+      e: f,
+      g: h +
+        i
+    }
+  },
+  g: [
+    h,
+    i,
+    j,
+    k
+  ]
+};
+
+var arrObject = {a:[
+  a,
+  b,
+  c
+]};
+
+var objArray = [{
+  a: b,
+  b: c,
+  c: d
+}];
+
+var arrArray = [[
+  a,
+  b,
+  c
+]];
+
+var objObject = {a:{
+  a: b,
+  b: c,
+  c: d
+}};
+
+
+switch (a) {
+  case 'a':
+    var a = 'b'; // ->
+    break;
+  case 'b':
+    var a = 'b';
+    break;
+  case 'c':
+    var a = 'b'; // <-
+    break;
+  case 'd':
+    var a = 'b';
+    break; // ->
+  case 'f':
+    var a = 'b';
+    break;
+  case 'g':     {
+    var a = 'b';
+    break;
+  }
+  case 'z':
+  default:
+    break; // <-
+}
+
+a.b('hi')
+  .c(a.b()) // <-
+  .d(); // <-
+
+if ( a ) {
+  if ( b ) {
+    d.e(f) // ->
+      .g() // ->
+      .h(); // ->
+
+    i.j(m)
+      .k()
+      .l();
+
+    n.o(p) // <-
+      .q() // <-
+      .r(); // <-
+  }
+}
+
+var a = b,
+    c = function () {
+      h = i; // ->
+      j = k;
+      l = m; // <-
+    },
+    e = {
+      f: g,
+      n: o,
+      p: q
+    },
+    r = [
+      s,
+      t,
+      u
+    ];
+
+var a = function () {
+  b = c; // ->
+  d = e;
+  f = g; // <-
+};
+
+function c1(a, b) {
+  if (a || (a &&
+    b)) {
+    return d;
+  }
+}
+
+if ( a
+  || b ) {
+  var x; // ->
+  var c,
+      d = function(a,
+                   b) { // <-
+                     a; // -> // Indent it specially with `vue/script-indent`.
+                     b;
+                     c; // <-
+                   }
+}
+
+
+a({
+  d: 1
+});
+
+a(
+  1
+);
+
+a(
+  b({
+    d: 1
+  })
+);
+
+a(
+  b(
+    c({
+      d: 1,
+      e: 1,
+      f: 1
+    })
+  )
+);
+
+a({ d: 1 });
+
+aa(
+  b({
+    c: d, // ->
+    e: f,
+    f: g
+  }) // ->
+);
+
+aaaaaa(
+  b,
+  c,
+  {
+    d: a
+  }
+);
+
+a(b, c,
+  d, e,
+  f, g
+);  // <-
+
+a(
+); // <-
+
+aaaaaa(
+  b,
+  c, {
+    d: a
+  }, {
+    e: f
+  }
+);
+
+a.b()
+  .c(function(){
+    var a;
+  }).d.e;
+
+if (a == 'b') {
+  if (c && d) e = f
+  else g('h').i('j')
+}
+
+a = function (b, c) {
+  return a(function () {
+    var d = e
+    var f = g
+    var h = i
+
+    if (!j) k('l', (m = n))
+    if (o) p
+    else if (q) r
+  })
+}
+
+var a = function() {
+  "b"
+    .replace(/a/, "a")
+    .replace(/bc?/, function(e) {
+      return "b" + (e.f === 2 ? "c" : "f");
+    })
+    .replace(/d/, "d");
+};
+
+$(b)
+  .on('a', 'b', function() { $(c).e('f'); })
+  .on('g', 'h', function() { $(i).j('k'); });
+
+a
+  .b('c',
+     'd');
+
+a
+  .b('c', [ 'd', function(e) {
+    e++;
+  }]);
+
+var a = function() {
+      a++;
+      b++; // <-
+      c++; // <-
+    },
+    b;
+
+var b = [
+      a,
+      b,
+      c
+    ],
+    c;
+
+var c = {
+      a: 1,
+      b: 2,
+      c: 3
+    },
+    d;
+
+// holes in arrays indentation
+x = [
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1
+];
+
+try {
+  a++;
+  b++; // <-
+  c++; // ->
+} catch (d) {
+  e++;
+  f++; // <-
+  g++; // ->
+} finally {
+  h++;
+  i++; // <-
+  j++; // ->
+}
+
+if (array.some(function(){
+  return true;
+})) {
+  a++; // ->
+  b++;
+  c++; // <-
+}
+
+var a = b.c(function() {
+      d++;
+    }),
+    e;
+
+switch (true) {
+  case (a
+    && b):
+  case (c // ->
+    && d):
+  case (e // <-
+    && f):
+  case (g
+    && h):
+    var i = j; // <-
+    var k = l;
+    var m = n; // ->
+}
+
+if (a) {
+  b();
+}
+else {
+  c(); // ->
+  d();
+  e(); // <-
+}
+
+if (a) b();
+else {
+  c(); // ->
+  d();
+  e(); // <-
+}
+
+if (a) {
+  b();
+} else c();
+
+if (a) {
+  b();
+}
+else c();
+
+a();
+
+if( "very very long multi line" +
+  "with weird indentation" ) {
+  b();
+  a(); // ->
+  c(); // <-
+}
+
+a( "very very long multi line" +
+  "with weird indentation", function() {
+  b();
+  a(); // ->
+  c(); // <-
+}); // <-
+
+a = function(content, dom) {
+  b();
+  c(); // <-
+  d(); // ->
+};
+
+a = function(content, dom) {
+  b();
+  c(); // <-
+  d(); // ->
+};
+
+a = function(content, dom) {
+  b(); // ->
+};
+
+a = function(content, dom) {
+  b(); // ->
+};
+
+a('This is a terribly long description youll ' +
+  'have to read', function () {
+  b(); // <-
+  c(); // <-
+}); // <-
+
+if (
+  array.some(function(){
+    return true;
+  })
+) {
+  a++; // ->
+  b++;
+  c++; // <-
+}
+
+function c2(d) {
+  return {
+    e: function(f, g) {
+    }
+  };
+}
+
+function a1(b) {
+  switch(x) {
+    case 1:
+      if (foo) {
+        return 5;
+      }
+  }
+}
+
+function a2(b) {
+  switch(x) {
+    case 1:
+      c;
+  }
+}
+
+function a3(b) {
+  switch(x) {
+    case 1: c;
+  }
+}
+
+function test() {
+  var a = 1;
+  {
+    a();
+  }
+}
+
+{
+  a();
+}
+
+function a4(b) {
+  switch(x) {
+    case 1:
+      { // <-
+        a(); // ->
+      }
+      break;
+    default:
+    {
+      b();
+    }
+  }
+}
+
+switch (a) {
+  default:
+    if (b)
+      c();
+}
+
+function test1(x) {
+  switch (x) {
+    case 1:
+      return function() {
+        var a = 5;
+        return a;
+      };
+  }
+}
+
+switch (a) {
+  default:
+    if (b)
+      c();
+}
+</script>
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/issue441.vue b/tests/fixtures/script-indent/issue441.vue
new file mode 100644
index 00000000..a6738f2a
--- /dev/null
+++ b/tests/fixtures/script-indent/issue441.vue
@@ -0,0 +1,10 @@
+<!--{}-->
+<script>
+o
+  .fn(() => {
+  });
+
+o2
+  .fn(() => {
+  });fn()
+</script>
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/issue443.vue b/tests/fixtures/script-indent/issue443.vue
new file mode 100644
index 00000000..4a8c356e
--- /dev/null
+++ b/tests/fixtures/script-indent/issue443.vue
@@ -0,0 +1,27 @@
+<!--{}-->
+<script>
+let v = [
+  o
+    .fn(
+    ),
+]
+
+let v2 = [
+  o2
+    .fn(
+    ),e2
+]
+
+var v3 = [() =>
+            1
+          ,o3
+            .fn(
+            ),e3]
+
+var v4 = [() =>
+            1
+          ,o4
+            .fn(
+            ),e4
+]
+</script>
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/sequence-expression-04.vue b/tests/fixtures/script-indent/sequence-expression-04.vue
new file mode 100644
index 00000000..f263bf52
--- /dev/null
+++ b/tests/fixtures/script-indent/sequence-expression-04.vue
@@ -0,0 +1,8 @@
+<!--{}-->
+<script>
+(
+  a +
+    b,2,
+  3
+)
+</script>
diff --git a/tests/lib/rules/require-prop-type-constructor.js b/tests/lib/rules/require-prop-type-constructor.js
index f0740385..fc6e79fc 100644
--- a/tests/lib/rules/require-prop-type-constructor.js
+++ b/tests/lib/rules/require-prop-type-constructor.js
@@ -37,7 +37,9 @@ ruleTester.run('require-prop-type-constructor', rule, {
             },
             lastProp: {
               type: [Number, Boolean]
-            }
+            },
+            nullProp: null,
+            nullTypeProp: { type: null }
           }
         }
       `
@@ -58,7 +60,8 @@ ruleTester.run('require-prop-type-constructor', rule, {
           },
           lastProp: {
             type: ['Boolean']
-          }
+          },
+          nullProp: 'null'
         }
       }
       `,
@@ -73,7 +76,8 @@ ruleTester.run('require-prop-type-constructor', rule, {
           },
           lastProp: {
             type: [Boolean]
-          }
+          },
+          nullProp: null
         }
       }
       `,
@@ -92,6 +96,9 @@ ruleTester.run('require-prop-type-constructor', rule, {
       }, {
         message: 'The "type" property should be a constructor.',
         line: 11
+      }, {
+        message: 'The "nullProp" property should be a constructor.',
+        line: 13
       }]
     },
     {
diff --git a/tests/lib/rules/valid-v-on.js b/tests/lib/rules/valid-v-on.js
index 3c02be86..f24e10dc 100644
--- a/tests/lib/rules/valid-v-on.js
+++ b/tests/lib/rules/valid-v-on.js
@@ -86,6 +86,16 @@ tester.run('valid-v-on', rule, {
     {
       filename: 'test.vue',
       code: '<template><div v-on="{a, b, c: d}"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.bar="foo"></div></template>',
+      options: [{ modifiers: ['bar'] }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-on:keydown.bar.aaa="foo"></div></template>',
+      options: [{ modifiers: ['bar', 'aaa'] }]
     }
   ],
   invalid: [
@@ -103,6 +113,18 @@ tester.run('valid-v-on', rule, {
       filename: 'test.vue',
       code: '<template><div @click></div></template>',
       errors: ["'v-on' directives require that attribute value or verb modifiers."]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.bar.aaa="foo"></div></template>',
+      errors: ["'v-on' directives don't support the modifier 'aaa'."],
+      options: [{ modifiers: ['bar'] }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.bar.aaa="foo"></div></template>',
+      errors: ["'v-on' directives don't support the modifier 'bar'."],
+      options: [{ modifiers: ['aaa'] }]
     }
   ]
 })