Repository: vuejs/eslint-plugin-vue
Pull request #209 (2017-10-09 15:34:34)

[New] Add `vue/attribute-order`
resolves https://github.com/vuejs/eslint-plugin-vue/issues/170

michalsnik: Hey @erindepew Are you willing to work on this further on? If yes - let us know and please see last comments in #209 for more informations about possible solutions :)
erindepew: @michalsnik sure! I was just waiting for the community to decide if they want a configuration option and if so what it should be. Are you referring to https://github.com/vuejs/eslint-plugin-vue/issues/170#issuecomment-336772966 ? 
michalsnik: Cool! Sort of, but you know what? Let's start with a simple implementation that does job well according to the official style guide, that is: let's pre-define groups of attributes and only allow people to change order of those groups in configuration, but not individual attributes.
Let's not overthink it yet.

So the default configuration could look just like this:
```
'vue/attributes-order': [2, {
  order: [
    'CONDITIONALS',
    'RENDER_MODIFIERS'
    // ...
  ],
}]
```

Groups should obviously reflect situation from style guide.

After it's done we can iterate and add custom groups support / think about possible edge scenarios etc. WDYT?
erindepew: @michalsnik I'm going to assume that it makes sense to push off ordering them alphabetically within those groups for a future iteration? 
erindepew: @michalsnik updated, let me know what you think! 
erindepew: @michalsnik yup, that all makes sense. Updated! 
michalsnik: Hey @erindepew, I'll take a closer look and test run soon :) So far it's looking very promising 🚀 

A little advice for the future though: please try not to rebase during review, as people that check your code after review once again can't see actual changes you performed in order to resolve given suggestions. Rebasing is good right before requesting review or after review :)

Patch:
diff --git a/README.md b/README.md
index d6b4d738..0ff26964 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,8 @@ module.exports = {
 All component-related rules are being applied to code that passes any of the following checks:
 
 * `Vue.component()` expression
+* `Vue.extend()` expression
+* `Vue.mixin()` expression
 * `export default {}` in `.vue` or `.jsx` file
 
 If you however want to take advantage of our rules in any of your custom objects that are Vue components, you might need to use special comment `// @vue/component` that marks object in the next line as a Vue component in any file, e.g.:
@@ -202,6 +204,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 
 |    | Rule ID | Description |
 |:---|:--------|:------------|
+|  | [vue/attributes-order](./docs/rules/attributes-order.md) | enforce order of attributes |
 | :wrench: | [vue/html-closing-bracket-newline](./docs/rules/html-closing-bracket-newline.md) | require or disallow a line break before tag's closing brackets |
 | :wrench: | [vue/html-closing-bracket-spacing](./docs/rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets |
 | :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |
diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md
new file mode 100644
index 00000000..f002033a
--- /dev/null
+++ b/docs/rules/attributes-order.md
@@ -0,0 +1,114 @@
+# enforce order of attributes (vue/attributes-order)
+
+## :book: Rule Details
+
+This rule aims to enfore ordering of component attributes. The default order is specified in the [Vue styleguide](https://vuejs.org/v2/style-guide/#Element-attribute-order-recommended) and is:
+- DEFINITION
+ex: 'is'
+- LIST_RENDERING
+ex: 'v-for item in items'
+- CONDITIONALS
+ex: 'v-if', 'v-else-if', 'v-else', 'v-show', 'v-cloak'
+- RENDER_MODIFIERS
+ex: 'v-once', 'v-pre'
+- GLOBAL
+ex: 'id'
+- UNIQUE
+ex: 'ref', 'key', 'slot'
+- BINDING
+ex: 'v-model', 'v-bind', ':property="foo"'
+- OTHER_ATTR
+ex: 'customProp="foo"'
+- EVENTS
+ex: '@click="functionCall"', 'v-on="event"'
+- CONTENT
+ex: 'v-text', 'v-html'
+
+:+1: Examples of **correct** code`:
+
+```html
+<div
+  is="header"
+  v-for="item in items"
+  v-if="!visible"
+  v-once
+  id="uniqueID"
+  ref="header"
+  v-model="headerData"
+  myProp="prop"
+  @click="functionCall"
+  v-text="textContent">
+</div>
+```
+
+```html
+<div
+  v-for="item in items"
+  v-if="!visible"
+  propOne="prop"
+  propTwo="prop"
+  propThree="prop"
+  @click="functionCall"
+  v-text="textContent">
+</div>
+```
+
+```html
+<div
+  propOne="prop"
+  propTwo="prop"
+  propThree="prop">
+</div>
+```
+
+:-1: Examples of **incorrect** code`:
+
+```html
+<div
+  ref="header"
+  v-for="item in items"
+  v-once id="uniqueID"
+  v-model="headerData"
+  myProp="prop"
+  v-if="!visible"
+  is="header"
+  @click="functionCall"
+  v-text="textContent">
+</div>
+```
+
+### `order`
+
+Specify custom order of attribute groups
+
+:+1: Examples of **correct** code with custom order`:
+
+```html
+<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT', 'DEFINITION'] }] -->
+<div
+  propOne="prop"
+  propTwo="prop"
+  is="header">
+</div>
+```
+
+```html
+<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
+<div
+  ref="header"
+  is="header"
+  propOne="prop"
+  propTwo="prop">
+</div>
+```
+
+:-1: Examples of **incorrect** code with custom order`:
+
+```html
+<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
+<div
+  ref="header"
+  propOne="prop"
+  is="header">
+</div>
+```
\ No newline at end of file
diff --git a/docs/rules/no-multi-spaces.md b/docs/rules/no-multi-spaces.md
index 196bee56..d68fe02b 100644
--- a/docs/rules/no-multi-spaces.md
+++ b/docs/rules/no-multi-spaces.md
@@ -5,7 +5,7 @@
 
 The `--fix` option on the command line can automatically fix some of the problems reported by this rule.
 
-This rule aims to remove multiple spaces in a row between attributes witch are not used for indentation.
+This rule aims to remove multiple spaces in a row between attributes which are not used for indentation.
 
 ## Rule Details
 
diff --git a/docs/rules/order-in-components.md b/docs/rules/order-in-components.md
index ba40ca20..460503a5 100644
--- a/docs/rules/order-in-components.md
+++ b/docs/rules/order-in-components.md
@@ -46,7 +46,7 @@ If you want you can change the order providing the optional configuration in you
 
 ``` json
 "vue/order-in-components": ["error", {
-  order: [
+  "order": [
     "el",
     "name",
     "parent",
diff --git a/docs/rules/prop-name-casing.md b/docs/rules/prop-name-casing.md
new file mode 100644
index 00000000..fcd8207f
--- /dev/null
+++ b/docs/rules/prop-name-casing.md
@@ -0,0 +1,35 @@
+# enforce specific casing for the Prop name in Vue components(prop-name-casing)
+
+This rule would enforce proper casing of props in vue components(camelCase).
+
+## :book: Rule Details
+
+(https://vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended).
+
+:+1: Examples of **correct** code for `camelCase`:
+
+```js
+export default {
+  props: {
+    greetingText: String
+  }
+}
+```
+
+:-1: Examples of **incorrect** code for `camelCase`:
+
+```js
+export default {
+  props: {
+    'greeting-text': String
+  }
+}
+```
+
+## :wrench: Options
+
+Default casing is set to `camelCase`.
+
+```
+"vue/prop-name-casing": ["error", "camelCase|snake_case"]
+```
diff --git a/lib/index.js b/lib/index.js
index b2871f54..01fbaee4 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -8,6 +8,7 @@
 module.exports = {
   rules: {
     'attribute-hyphenation': require('./rules/attribute-hyphenation'),
+    'attributes-order': require('./rules/attributes-order'),
     'comment-directive': require('./rules/comment-directive'),
     'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
     'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js
index c428f0fd..79bc87fb 100644
--- a/lib/rules/attribute-hyphenation.js
+++ b/lib/rules/attribute-hyphenation.js
@@ -16,7 +16,7 @@ module.exports = {
     docs: {
       description: 'enforce attribute naming style in template',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/attribute-hyphenation.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/attribute-hyphenation.md'
     },
     fixable: 'code',
     schema: [
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
new file mode 100644
index 00000000..e5cd19dd
--- /dev/null
+++ b/lib/rules/attributes-order.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview enforce ordering of attributes
+ * @author Erin Depew
+ */
+'use strict'
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function getAttributeType (name, isDirective) {
+  if (isDirective) {
+    if (name === 'for') {
+      return 'LIST_RENDERING'
+    } else if (name === 'if' || name === 'else-if' || name === 'else' || name === 'show' || name === 'cloak') {
+      return 'CONDITIONALS'
+    } else if (name === 'pre' || name === 'once') {
+      return 'RENDER_MODIFIERS'
+    } else if (name === 'model' || name === 'bind') {
+      return 'BINDING'
+    } else if (name === 'on') {
+      return 'EVENTS'
+    } else if (name === 'html' || name === 'text') {
+      return 'CONTENT'
+    }
+  } else {
+    if (name === 'is') {
+      return 'DEFINITION'
+    } else if (name === 'id') {
+      return 'GLOBAL'
+    } else if (name === 'ref' || name === 'key' || name === 'slot') {
+      return 'UNIQUE'
+    } else {
+      return 'OTHER_ATTR'
+    }
+  }
+}
+function getPosition (attribute, attributeOrder) {
+  const attributeType = getAttributeType(attribute.key.name, attribute.directive)
+  return attributeOrder.indexOf(attributeType)
+}
+
+function create (context) {
+  const sourceCode = context.getSourceCode()
+  let attributeOrder = ['DEFINITION', 'LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT']
+  if (context.options[0] && context.options[0].order) {
+    attributeOrder = context.options[0].order
+  }
+  let currentPosition
+  let previousNode
+
+  function reportIssue (node, previousNode) {
+    const currentNode = sourceCode.getText(node.key)
+    const prevNode = sourceCode.getText(previousNode.key)
+    context.report({
+      node: node.key,
+      loc: node.loc,
+      message: `Attribute "${currentNode}" should go before "${prevNode}".`,
+      data: {
+        currentNode
+      }
+    })
+  }
+
+  return utils.defineTemplateBodyVisitor(context, {
+    'VStartTag' () {
+      currentPosition = -1
+      previousNode = null
+    },
+    'VAttribute' (node) {
+      if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributeOrder))) {
+        currentPosition = getPosition(node, attributeOrder)
+        previousNode = node
+      } else {
+        reportIssue(node, previousNode)
+      }
+    }
+  })
+}
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'enforce order of attributes',
+      category: 'recommended'
+    },
+    fixable: null,
+    schema: {
+      type: 'array',
+      properties: {
+        order: {
+          items: {
+            type: 'string'
+          },
+          maxItems: 10,
+          minItems: 10
+        }
+      }
+    }
+  },
+  create
+}
diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js
index 69d381ff..62472298 100644
--- a/lib/rules/comment-directive.js
+++ b/lib/rules/comment-directive.js
@@ -109,7 +109,7 @@ module.exports = {
     docs: {
       description: 'support comment-directives in `<template>`',
       category: 'base',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/comment-directive.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/comment-directive.md'
     },
     schema: []
   },
diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js
index 724bef47..207c6929 100644
--- a/lib/rules/html-closing-bracket-newline.js
+++ b/lib/rules/html-closing-bracket-newline.js
@@ -32,7 +32,7 @@ module.exports = {
     docs: {
       description: "require or disallow a line break before tag's closing brackets",
       category: undefined,
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-closing-bracket-newline.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-closing-bracket-newline.md'
     },
     fixable: 'whitespace',
     schema: [{
diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js
index 198d7dd4..3678a0af 100644
--- a/lib/rules/html-closing-bracket-spacing.js
+++ b/lib/rules/html-closing-bracket-spacing.js
@@ -53,7 +53,7 @@ module.exports = {
     docs: {
       description: 'require or disallow a space before tag\'s closing brackets',
       category: undefined,
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-closing-bracket-spacing.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-closing-bracket-spacing.md'
     },
     schema: [{
       type: 'object',
diff --git a/lib/rules/html-end-tags.js b/lib/rules/html-end-tags.js
index 69222ec6..f6fc80f8 100644
--- a/lib/rules/html-end-tags.js
+++ b/lib/rules/html-end-tags.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce end tag style',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-end-tags.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-end-tags.md'
     },
     fixable: 'code',
     schema: []
diff --git a/lib/rules/html-indent.js b/lib/rules/html-indent.js
index 3dbc2754..2cc83b5a 100644
--- a/lib/rules/html-indent.js
+++ b/lib/rules/html-indent.js
@@ -29,7 +29,7 @@ module.exports = {
     docs: {
       description: 'enforce consistent indentation in `<template>`',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-indent.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-indent.md'
     },
     fixable: 'whitespace',
     schema: [
diff --git a/lib/rules/html-quotes.js b/lib/rules/html-quotes.js
index e8e9295f..fdbbdd18 100644
--- a/lib/rules/html-quotes.js
+++ b/lib/rules/html-quotes.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce quotes style of HTML attributes',
       category: 'recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-quotes.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-quotes.md'
     },
     fixable: 'code',
     schema: [
diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js
index 51aa4b21..2d0c17d8 100644
--- a/lib/rules/html-self-closing.js
+++ b/lib/rules/html-self-closing.js
@@ -88,7 +88,7 @@ module.exports = {
     docs: {
       description: 'enforce self-closing style',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/html-self-closing.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/html-self-closing.md'
     },
     fixable: 'code',
     schema: {
diff --git a/lib/rules/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js
index 653ed18f..3fead909 100644
--- a/lib/rules/jsx-uses-vars.js
+++ b/lib/rules/jsx-uses-vars.js
@@ -39,7 +39,7 @@ module.exports = {
     docs: {
       description: 'prevent variables used in JSX to be marked as unused', // eslint-disable-line consistent-docs-description
       category: 'base',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/jsx-uses-vars.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/jsx-uses-vars.md'
     },
     schema: []
   },
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
index d68e8f2d..0d05f0be 100644
--- a/lib/rules/max-attributes-per-line.js
+++ b/lib/rules/max-attributes-per-line.js
@@ -14,9 +14,9 @@ module.exports = {
     docs: {
       description: 'enforce the maximum number of attributes per line',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/max-attributes-per-line.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/max-attributes-per-line.md'
     },
-    fixable: null,
+    fixable: 'whitespace', // or "code" or "whitespace"
     schema: [
       {
         type: 'object',
@@ -129,14 +129,15 @@ module.exports = {
     }
 
     function showErrors (attributes, node) {
-      attributes.forEach((prop) => {
+      attributes.forEach((prop, i) => {
         context.report({
           node: prop,
           loc: prop.loc,
           message: 'Attribute "{{propName}}" should be on a new line.',
           data: {
             propName: prop.key.name
-          }
+          },
+          fix: i === 0 ? (fixer) => fixer.insertTextBefore(prop, '\n') : undefined
         })
       })
     }
diff --git a/lib/rules/mustache-interpolation-spacing.js b/lib/rules/mustache-interpolation-spacing.js
index 4ef9252d..7f2113f1 100644
--- a/lib/rules/mustache-interpolation-spacing.js
+++ b/lib/rules/mustache-interpolation-spacing.js
@@ -19,7 +19,7 @@ module.exports = {
     docs: {
       description: 'enforce unified spacing in mustache interpolations',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/mustache-interpolation-spacing.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/mustache-interpolation-spacing.md'
     },
     fixable: 'whitespace',
     schema: [
diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js
index 42ecb692..822f4610 100644
--- a/lib/rules/name-property-casing.js
+++ b/lib/rules/name-property-casing.js
@@ -17,7 +17,7 @@ module.exports = {
     docs: {
       description: 'enforce specific casing for the name property in Vue components',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/name-property-casing.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/name-property-casing.md'
     },
     fixable: 'code', // or "code" or "whitespace"
     schema: [
diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js
index f73948e6..4e7584e6 100644
--- a/lib/rules/no-async-in-computed-properties.js
+++ b/lib/rules/no-async-in-computed-properties.js
@@ -64,7 +64,7 @@ module.exports = {
     docs: {
       description: 'disallow asynchronous actions in computed properties',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-async-in-computed-properties.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-async-in-computed-properties.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/no-confusing-v-for-v-if.js b/lib/rules/no-confusing-v-for-v-if.js
index 76f3f8d4..34831cf2 100644
--- a/lib/rules/no-confusing-v-for-v-if.js
+++ b/lib/rules/no-confusing-v-for-v-if.js
@@ -39,7 +39,7 @@ module.exports = {
     docs: {
       description: 'disallow confusing `v-for` and `v-if` on the same element',
       category: 'recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-confusing-v-for-v-if.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-confusing-v-for-v-if.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index 2dde2eed..8290cb13 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -17,7 +17,7 @@ module.exports = {
     docs: {
       description: 'disallow duplication of field names',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-dupe-keys.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-dupe-keys.md'
     },
     fixable: null, // or "code" or "whitespace"
     schema: [
diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js
index d239de61..48480503 100644
--- a/lib/rules/no-duplicate-attributes.js
+++ b/lib/rules/no-duplicate-attributes.js
@@ -39,7 +39,7 @@ module.exports = {
     docs: {
       description: 'disallow duplication of attributes',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-duplicate-attributes.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-duplicate-attributes.md'
     },
     fixable: null,
 
diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js
index b0b4e7c3..c96004b7 100644
--- a/lib/rules/no-multi-spaces.js
+++ b/lib/rules/no-multi-spaces.js
@@ -13,7 +13,7 @@ module.exports = {
     docs: {
       description: 'disallow multiple spaces',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-multi-spaces.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-multi-spaces.md'
     },
     fixable: 'whitespace', // or "code" or "whitespace"
     schema: []
diff --git a/lib/rules/no-parsing-error.js b/lib/rules/no-parsing-error.js
index 7a5d3f75..6c3321b3 100644
--- a/lib/rules/no-parsing-error.js
+++ b/lib/rules/no-parsing-error.js
@@ -58,7 +58,7 @@ module.exports = {
     docs: {
       description: 'disallow parsing errors in `<template>`',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-parsing-error.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-parsing-error.md'
     },
     fixable: null,
     schema: [
diff --git a/lib/rules/no-reserved-keys.js b/lib/rules/no-reserved-keys.js
index 6e49f579..f2385086 100644
--- a/lib/rules/no-reserved-keys.js
+++ b/lib/rules/no-reserved-keys.js
@@ -18,7 +18,7 @@ module.exports = {
     docs: {
       description: 'disallow overwriting reserved keys',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-reserved-keys.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-reserved-keys.md'
     },
     fixable: null,
     schema: [
diff --git a/lib/rules/no-shared-component-data.js b/lib/rules/no-shared-component-data.js
index d946a5cb..b69d0885 100644
--- a/lib/rules/no-shared-component-data.js
+++ b/lib/rules/no-shared-component-data.js
@@ -40,7 +40,7 @@ module.exports = {
     docs: {
       description: "enforce component's data property to be a function",
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-shared-component-data.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-shared-component-data.md'
     },
     fixable: 'code',
     schema: []
diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js
index 8cb33354..0dd05674 100644
--- a/lib/rules/no-side-effects-in-computed-properties.js
+++ b/lib/rules/no-side-effects-in-computed-properties.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'disallow side effects in computed properties',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-side-effects-in-computed-properties.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-side-effects-in-computed-properties.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/no-template-key.js b/lib/rules/no-template-key.js
index 97db9c8f..fb418fff 100644
--- a/lib/rules/no-template-key.js
+++ b/lib/rules/no-template-key.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'disallow `key` attribute on `<template>`',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-template-key.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-template-key.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/no-textarea-mustache.js b/lib/rules/no-textarea-mustache.js
index 833e5225..96ece3e4 100644
--- a/lib/rules/no-textarea-mustache.js
+++ b/lib/rules/no-textarea-mustache.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'disallow mustaches in `<textarea>`',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-textarea-mustache.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-textarea-mustache.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 442269fe..955b2eb0 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'disallow unused variable definitions of v-for directives or scope attributes',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/no-unused-vars.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/no-unused-vars.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js
index cb27e3ed..3d9a21af 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -5,6 +5,7 @@
 'use strict'
 
 const utils = require('../utils')
+const Traverser = require('eslint/lib/util/traverser')
 
 const defaultOrder = [
   'el',
@@ -56,6 +57,75 @@ function getOrderMap (order) {
   return orderMap
 }
 
+function isComma (node) {
+  return node.type === 'Punctuator' && node.value === ','
+}
+
+const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**']
+const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>']
+const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<=']
+const RELATIONAL_OPERATORS = ['in', 'instanceof']
+const ALL_BINARY_OPERATORS = [].concat(
+  ARITHMETIC_OPERATORS,
+  BITWISE_OPERATORS,
+  COMPARISON_OPERATORS,
+  RELATIONAL_OPERATORS
+)
+const LOGICAL_OPERATORS = ['&&', '||']
+
+/*
+ * Result `true` if the node is sure that there are no side effects
+ *
+ * Currently known side effects types
+ *
+ * node.type === 'CallExpression'
+ * node.type === 'NewExpression'
+ * node.type === 'UpdateExpression'
+ * node.type === 'AssignmentExpression'
+ * node.type === 'TaggedTemplateExpression'
+ * node.type === 'UnaryExpression' && node.operator === 'delete'
+ *
+ * @param  {ASTNode} node target node
+ * @param  {Object} visitorKeys sourceCode.visitorKey
+ * @returns {Boolean} no side effects
+ */
+function isNotSideEffectsNode (node, visitorKeys) {
+  let result = true
+  new Traverser().traverse(node, {
+    visitorKeys,
+    enter (node, parent) {
+      if (
+        node.type === 'FunctionExpression' ||
+        node.type === 'Identifier' ||
+        node.type === 'Literal' ||
+        // es2015
+        node.type === 'ArrowFunctionExpression' ||
+        node.type === 'TemplateElement'
+      ) {
+        // no side effects node
+        this.skip()
+      } else if (
+        node.type !== 'Property' &&
+        node.type !== 'ObjectExpression' &&
+        node.type !== 'ArrayExpression' &&
+        (node.type !== 'UnaryExpression' || ['!', '~', '+', '-', 'typeof'].indexOf(node.operator) < 0) &&
+        (node.type !== 'BinaryExpression' || ALL_BINARY_OPERATORS.indexOf(node.operator) < 0) &&
+        (node.type !== 'LogicalExpression' || LOGICAL_OPERATORS.indexOf(node.operator) < 0) &&
+        node.type !== 'MemberExpression' &&
+        node.type !== 'ConditionalExpression' &&
+        // es2015
+        node.type !== 'SpreadElement' &&
+        node.type !== 'TemplateLiteral'
+      ) {
+        // Can not be sure that a node has no side effects
+        result = false
+        this.break()
+      }
+    }
+  })
+  return result
+}
+
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -65,9 +135,9 @@ module.exports = {
     docs: {
       description: 'enforce order of properties in components',
       category: 'recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/order-in-components.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/order-in-components.md'
     },
-    fixable: null,
+    fixable: 'code', // null or "code" or "whitespace"
     schema: [
       {
         type: 'object',
@@ -86,6 +156,7 @@ module.exports = {
     const order = options.order || defaultOrder
     const extendedOrder = order.map(property => groups[property] || property)
     const orderMap = getOrderMap(extendedOrder)
+    const sourceCode = context.getSourceCode()
 
     function checkOrder (propertiesNodes, orderMap) {
       const properties = propertiesNodes
@@ -109,6 +180,35 @@ module.exports = {
               name: property.name,
               firstUnorderedPropertyName: firstUnorderedProperty.name,
               line
+            },
+            fix (fixer) {
+              const propertyNode = property.parent
+              const firstUnorderedPropertyNode = firstUnorderedProperty.parent
+              const hasSideEffectsPossibility = propertiesNodes
+                .slice(
+                  propertiesNodes.indexOf(firstUnorderedPropertyNode),
+                  propertiesNodes.indexOf(propertyNode) + 1
+                )
+                .some((property) => !isNotSideEffectsNode(property, sourceCode.visitorKeys))
+              if (hasSideEffectsPossibility) {
+                return undefined
+              }
+              const comma = sourceCode.getTokenAfter(propertyNode)
+              const hasAfterComma = isComma(comma)
+
+              const codeStart = sourceCode.getTokenBefore(propertyNode).range[1] // to include comments
+              const codeEnd = hasAfterComma ? comma.range[1] : propertyNode.range[1]
+
+              const propertyCode = sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? '' : ',')
+              const insertTarget = sourceCode.getTokenBefore(firstUnorderedPropertyNode)
+              // If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by:
+              // return [
+              //   fixer.removeRange([codeStart, codeEnd]),
+              //   fixer.insertTextAfter(insertTarget, propertyCode)
+              // ]
+              const insertStart = insertTarget.range[1]
+              const newCode = propertyCode + sourceCode.text.slice(insertStart, codeStart)
+              return fixer.replaceTextRange([insertStart, codeEnd], newCode)
             }
           })
         }
diff --git a/lib/rules/prop-name-casing.js b/lib/rules/prop-name-casing.js
new file mode 100644
index 00000000..02070af2
--- /dev/null
+++ b/lib/rules/prop-name-casing.js
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview Requires specific casing for the Prop name in Vue components
+ * @author Yu Kimura
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+const allowedCaseOptions = ['camelCase', 'snake_case']
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function create (context) {
+  const options = context.options[0]
+  const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'camelCase'
+  const converter = casing.getConverter(caseType)
+
+  // ----------------------------------------------------------------------
+  // Public
+  // ----------------------------------------------------------------------
+
+  return utils.executeOnVue(context, (obj) => {
+    const node = obj.properties.find(p =>
+      p.type === 'Property' &&
+      p.key.type === 'Identifier' &&
+      p.key.name === 'props' &&
+      (p.value.type === 'ObjectExpression' || p.value.type === 'ArrayExpression')
+    )
+
+    if (!node) return
+
+    const items = node.value.type === 'ObjectExpression' ? node.value.properties : node.value.elements
+    for (const item of items) {
+      if (item.type !== 'Property') {
+        return
+      }
+
+      const propName = item.key.type === 'Literal' ? item.key.value : item.key.name
+      const convertedName = converter(propName)
+      if (convertedName !== propName) {
+        context.report({
+          node: item,
+          message: 'Prop "{{name}}" is not in {{caseType}}.',
+          data: {
+            name: propName,
+            caseType: caseType
+          }
+        })
+      }
+    }
+  })
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'enforce specific casing for the Prop name in Vue components',
+      category: undefined // 'strongly-recommended'
+    },
+    fixable: null,  // or "code" or "whitespace"
+    schema: [
+      {
+        enum: allowedCaseOptions
+      }
+    ]
+  },
+
+  create
+}
diff --git a/lib/rules/require-component-is.js b/lib/rules/require-component-is.js
index 1c945ae5..baad4814 100644
--- a/lib/rules/require-component-is.js
+++ b/lib/rules/require-component-is.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'require `v-bind:is` of `<component>` elements',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-component-is.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-component-is.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js
index 6a3c0500..b53d8ec0 100644
--- a/lib/rules/require-default-prop.js
+++ b/lib/rules/require-default-prop.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'require default value for props',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-default-prop.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-default-prop.md'
     },
     fixable: null, // or "code" or "whitespace"
     schema: []
diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js
index 44002b2c..441060e8 100644
--- a/lib/rules/require-prop-types.js
+++ b/lib/rules/require-prop-types.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'require type definitions in props',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-prop-types.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-prop-types.md'
     },
     fixable: null, // or "code" or "whitespace"
     schema: [
diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js
index decedbdb..036254c7 100644
--- a/lib/rules/require-render-return.js
+++ b/lib/rules/require-render-return.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'enforce render function to always return value',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-render-return.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-render-return.md'
     },
     fixable: null, // or "code" or "whitespace"
     schema: []
diff --git a/lib/rules/require-v-for-key.js b/lib/rules/require-v-for-key.js
index d007f6f4..bb0be010 100644
--- a/lib/rules/require-v-for-key.js
+++ b/lib/rules/require-v-for-key.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'require `v-bind:key` with `v-for` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-v-for-key.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-v-for-key.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js
index e3c07c39..63da08d9 100644
--- a/lib/rules/require-valid-default-prop.js
+++ b/lib/rules/require-valid-default-prop.js
@@ -24,7 +24,7 @@ module.exports = {
     docs: {
       description: 'enforce props default values to be valid',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/require-valid-default-prop.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/require-valid-default-prop.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js
index fc3584ee..fec1d377 100644
--- a/lib/rules/return-in-computed-property.js
+++ b/lib/rules/return-in-computed-property.js
@@ -15,7 +15,7 @@ module.exports = {
     docs: {
       description: 'enforce that a return statement is present in computed property',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/return-in-computed-property.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/return-in-computed-property.md'
     },
     fixable: null, // or "code" or "whitespace"
     schema: [
diff --git a/lib/rules/script-indent.js b/lib/rules/script-indent.js
index 1f6f74ee..44e80050 100644
--- a/lib/rules/script-indent.js
+++ b/lib/rules/script-indent.js
@@ -19,7 +19,7 @@ module.exports = {
     docs: {
       description: 'enforce consistent indentation in `<script>`',
       category: undefined,
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/script-indent.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/script-indent.md'
     },
     fixable: 'whitespace',
     schema: [
diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js
index 9813133b..c51abddd 100644
--- a/lib/rules/this-in-template.js
+++ b/lib/rules/this-in-template.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce usage of `this` in template',
       category: 'recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/this-in-template.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/this-in-template.md'
     },
     fixable: null,
     schema: [
diff --git a/lib/rules/v-bind-style.js b/lib/rules/v-bind-style.js
index e4d3e98b..56c1d172 100644
--- a/lib/rules/v-bind-style.js
+++ b/lib/rules/v-bind-style.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce `v-bind` directive style',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/v-bind-style.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/v-bind-style.md'
     },
     fixable: 'code',
     schema: [
diff --git a/lib/rules/v-on-style.js b/lib/rules/v-on-style.js
index bf406884..a3e48266 100644
--- a/lib/rules/v-on-style.js
+++ b/lib/rules/v-on-style.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce `v-on` directive style',
       category: 'strongly-recommended',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/v-on-style.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/v-on-style.md'
     },
     fixable: 'code',
     schema: [
diff --git a/lib/rules/valid-template-root.js b/lib/rules/valid-template-root.js
index 21d6604f..849761bf 100644
--- a/lib/rules/valid-template-root.js
+++ b/lib/rules/valid-template-root.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid template root',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-template-root.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-template-root.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-bind.js b/lib/rules/valid-v-bind.js
index b82105a9..867050b4 100644
--- a/lib/rules/valid-v-bind.js
+++ b/lib/rules/valid-v-bind.js
@@ -26,7 +26,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-bind` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-bind.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-bind.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-cloak.js b/lib/rules/valid-v-cloak.js
index 6bdf9f55..17aef670 100644
--- a/lib/rules/valid-v-cloak.js
+++ b/lib/rules/valid-v-cloak.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-cloak` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-cloak.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-cloak.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-else-if.js b/lib/rules/valid-v-else-if.js
index cdc5e170..5b89d4e1 100644
--- a/lib/rules/valid-v-else-if.js
+++ b/lib/rules/valid-v-else-if.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-else-if` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-else-if.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-else-if.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-else.js b/lib/rules/valid-v-else.js
index 6a08523c..02e0c10f 100644
--- a/lib/rules/valid-v-else.js
+++ b/lib/rules/valid-v-else.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-else` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-else.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-else.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-for.js b/lib/rules/valid-v-for.js
index 479f9b84..028df34f 100644
--- a/lib/rules/valid-v-for.js
+++ b/lib/rules/valid-v-for.js
@@ -106,7 +106,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-for` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-for.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-for.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-html.js b/lib/rules/valid-v-html.js
index 302fef51..e41fdf88 100644
--- a/lib/rules/valid-v-html.js
+++ b/lib/rules/valid-v-html.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-html` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-html.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-html.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-if.js b/lib/rules/valid-v-if.js
index e08d59b2..af1b47fb 100644
--- a/lib/rules/valid-v-if.js
+++ b/lib/rules/valid-v-if.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-if` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-if.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-if.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-model.js b/lib/rules/valid-v-model.js
index fc19ba89..22a6c641 100644
--- a/lib/rules/valid-v-model.js
+++ b/lib/rules/valid-v-model.js
@@ -82,7 +82,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-model` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-model.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-model.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js
index ef0f4baa..f84412bb 100644
--- a/lib/rules/valid-v-on.js
+++ b/lib/rules/valid-v-on.js
@@ -23,6 +23,88 @@ const VALID_MODIFIERS = new Set([
 const VERB_MODIFIERS = new Set([
   'stop', 'prevent'
 ])
+// https://www.w3.org/TR/uievents-key/
+const KEY_ALIASES = new Set([
+  'unidentified', 'alt', 'alt-graph', 'caps-lock', 'control', 'fn', 'fn-lock',
+  'meta', 'num-lock', 'scroll-lock', 'shift', 'symbol', 'symbol-lock', 'hyper',
+  'super', 'enter', 'tab', 'arrow-down', 'arrow-left', 'arrow-right',
+  'arrow-up', 'end', 'home', 'page-down', 'page-up', 'backspace', 'clear',
+  'copy', 'cr-sel', 'cut', 'delete', 'erase-eof', 'ex-sel', 'insert', 'paste',
+  'redo', 'undo', 'accept', 'again', 'attn', 'cancel', 'context-menu', 'escape',
+  'execute', 'find', 'help', 'pause', 'select', 'zoom-in', 'zoom-out',
+  'brightness-down', 'brightness-up', 'eject', 'log-off', 'power',
+  'print-screen', 'hibernate', 'standby', 'wake-up', 'all-candidates',
+  'alphanumeric', 'code-input', 'compose', 'convert', 'dead', 'final-mode',
+  'group-first', 'group-last', 'group-next', 'group-previous', 'mode-change',
+  'next-candidate', 'non-convert', 'previous-candidate', 'process',
+  'single-candidate', 'hangul-mode', 'hanja-mode', 'junja-mode', 'eisu',
+  'hankaku', 'hiragana', 'hiragana-katakana', 'kana-mode', 'kanji-mode',
+  'katakana', 'romaji', 'zenkaku', 'zenkaku-hankaku', 'f1', 'f2', 'f3', 'f4',
+  'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12', 'soft1', 'soft2', 'soft3',
+  'soft4', 'channel-down', 'channel-up', 'close', 'mail-forward', 'mail-reply',
+  'mail-send', 'media-close', 'media-fast-forward', 'media-pause',
+  'media-play-pause', 'media-record', 'media-rewind', 'media-stop',
+  'media-track-next', 'media-track-previous', 'new', 'open', 'print', 'save',
+  'spell-check', 'key11', 'key12', 'audio-balance-left', 'audio-balance-right',
+  'audio-bass-boost-down', 'audio-bass-boost-toggle', 'audio-bass-boost-up',
+  'audio-fader-front', 'audio-fader-rear', 'audio-surround-mode-next',
+  'audio-treble-down', 'audio-treble-up', 'audio-volume-down',
+  'audio-volume-up', 'audio-volume-mute', 'microphone-toggle',
+  'microphone-volume-down', 'microphone-volume-up', 'microphone-volume-mute',
+  'speech-correction-list', 'speech-input-toggle', 'launch-application1',
+  'launch-application2', 'launch-calendar', 'launch-contacts', 'launch-mail',
+  'launch-media-player', 'launch-music-player', 'launch-phone',
+  'launch-screen-saver', 'launch-spreadsheet', 'launch-web-browser',
+  'launch-web-cam', 'launch-word-processor', 'browser-back',
+  'browser-favorites', 'browser-forward', 'browser-home', 'browser-refresh',
+  'browser-search', 'browser-stop', 'app-switch', 'call', 'camera',
+  'camera-focus', 'end-call', 'go-back', 'go-home', 'headset-hook',
+  'last-number-redial', 'notification', 'manner-mode', 'voice-dial', 't-v',
+  't-v3-d-mode', 't-v-antenna-cable', 't-v-audio-description',
+  't-v-audio-description-mix-down', 't-v-audio-description-mix-up',
+  't-v-contents-menu', 't-v-data-service', 't-v-input', 't-v-input-component1',
+  't-v-input-component2', 't-v-input-composite1', 't-v-input-composite2',
+  't-v-input-h-d-m-i1', 't-v-input-h-d-m-i2', 't-v-input-h-d-m-i3',
+  't-v-input-h-d-m-i4', 't-v-input-v-g-a1', 't-v-media-context', 't-v-network',
+  't-v-number-entry', 't-v-power', 't-v-radio-service', 't-v-satellite',
+  't-v-satellite-b-s', 't-v-satellite-c-s', 't-v-satellite-toggle',
+  't-v-terrestrial-analog', 't-v-terrestrial-digital', 't-v-timer',
+  'a-v-r-input', 'a-v-r-power', 'color-f0-red', 'color-f1-green',
+  'color-f2-yellow', 'color-f3-blue', 'color-f4-grey', 'color-f5-brown',
+  'closed-caption-toggle', 'dimmer', 'display-swap', 'd-v-r', 'exit',
+  'favorite-clear0', 'favorite-clear1', 'favorite-clear2', 'favorite-clear3',
+  'favorite-recall0', 'favorite-recall1', 'favorite-recall2',
+  'favorite-recall3', 'favorite-store0', 'favorite-store1', 'favorite-store2',
+  'favorite-store3', 'guide', 'guide-next-day', 'guide-previous-day', 'info',
+  'instant-replay', 'link', 'list-program', 'live-content', 'lock',
+  'media-apps', 'media-last', 'media-skip-backward', 'media-skip-forward',
+  'media-step-backward', 'media-step-forward', 'media-top-menu', 'navigate-in',
+  'navigate-next', 'navigate-out', 'navigate-previous', 'next-favorite-channel',
+  'next-user-profile', 'on-demand', 'pairing', 'pin-p-down', 'pin-p-move',
+  'pin-p-toggle', 'pin-p-up', 'play-speed-down', 'play-speed-reset',
+  'play-speed-up', 'random-toggle', 'rc-low-battery', 'record-speed-next',
+  'rf-bypass', 'scan-channels-toggle', 'screen-mode-next', 'settings',
+  'split-screen-toggle', 's-t-b-input', 's-t-b-power', 'subtitle', 'teletext',
+  'video-mode-next', 'wink', 'zoom-toggle', 'audio-volume-down',
+  'audio-volume-up', 'audio-volume-mute', 'browser-back', 'browser-forward',
+  'channel-down', 'channel-up', 'context-menu', 'eject', 'end', 'enter', 'home',
+  'media-fast-forward', 'media-play', 'media-play-pause', 'media-record',
+  'media-rewind', 'media-stop', 'media-next-track', 'media-pause',
+  'media-previous-track', 'power', 'unidentified'
+])
+
+function isValidModifier (modifier) {
+  return (
+    // built-in aliases
+    VALID_MODIFIERS.has(modifier) ||
+    // keyCode
+    Number.isInteger(parseInt(modifier, 10)) ||
+    // keyAlias (an Unicode character)
+    Array.from(modifier).length === 1 ||
+    // keyAlias (special keys)
+    KEY_ALIASES.has(modifier)
+  )
+}
 
 // ------------------------------------------------------------------------------
 // Rule Definition
@@ -33,7 +115,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-on` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-on.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-on.md'
     },
     fixable: null,
     schema: []
@@ -43,7 +125,7 @@ module.exports = {
     return utils.defineTemplateBodyVisitor(context, {
       "VAttribute[directive=true][key.name='on']" (node) {
         for (const modifier of node.key.modifiers) {
-          if (!VALID_MODIFIERS.has(modifier) && !Number.isInteger(parseInt(modifier, 10))) {
+          if (!isValidModifier(modifier)) {
             context.report({
               node,
               loc: node.loc,
diff --git a/lib/rules/valid-v-once.js b/lib/rules/valid-v-once.js
index 7f01e354..3d4c54e5 100644
--- a/lib/rules/valid-v-once.js
+++ b/lib/rules/valid-v-once.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-once` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-once.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-once.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-pre.js b/lib/rules/valid-v-pre.js
index 0ff88a86..c0748758 100644
--- a/lib/rules/valid-v-pre.js
+++ b/lib/rules/valid-v-pre.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-pre` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-pre.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-pre.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-show.js b/lib/rules/valid-v-show.js
index 147e86ec..eb617100 100644
--- a/lib/rules/valid-v-show.js
+++ b/lib/rules/valid-v-show.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-show` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-show.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-show.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/valid-v-text.js b/lib/rules/valid-v-text.js
index 19bf9d46..c33bd6e0 100644
--- a/lib/rules/valid-v-text.js
+++ b/lib/rules/valid-v-text.js
@@ -20,7 +20,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `v-text` directives',
       category: 'essential',
-      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/valid-v-text.md'
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/valid-v-text.md'
     },
     fixable: null,
     schema: []
diff --git a/lib/utils/casing.js b/lib/utils/casing.js
index 2baca235..db73a5a2 100644
--- a/lib/utils/casing.js
+++ b/lib/utils/casing.js
@@ -14,6 +14,18 @@ function kebabCase (str) {
     .toLowerCase()
 }
 
+/**
+ * Convert text to snake_case
+ * @param {string} str Text to be converted
+ * @return {string}
+ */
+function snakeCase (str) {
+  return str
+    .replace(/([a-z])([A-Z])/g, match => match[0] + '_' + match[1])
+    .replace(invalidChars, '_')
+    .toLowerCase()
+}
+
 /**
  * Convert text to camelCase
  * @param {string} str Text to be converted
@@ -42,6 +54,7 @@ function pascalCase (str) {
 
 const convertersMap = {
   'kebab-case': kebabCase,
+  'snake_case': snakeCase,
   'camelCase': camelCase,
   'PascalCase': pascalCase
 }
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 704e8726..dfc89d57 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -18,6 +18,7 @@ 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.
@@ -244,6 +245,21 @@ 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.
  *
@@ -262,6 +278,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token|Token[]} token The token to set.
    * @param {number} offset The offset of the tokens.
    * @param {Token} baseToken The token of the base offset.
+   * @param {boolean} [trivial=false] The flag for trivial tokens.
    * @returns {void}
    */
   function setOffset (token, offset, baseToken) {
@@ -352,9 +369,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         if (lastToken != null) {
           t = lastToken
           while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= elementTokens.firstToken.range[0]) {
-            if (lastToken.loc.end.line !== t.loc.start.line) {
-              alignTokens.push(t)
-            }
+            alignTokens.push(t)
           }
         }
         alignTokens.push(elementTokens.firstToken)
@@ -550,13 +565,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @returns {number} Correct indentation. If it failed to calculate then `Number.MAX_SAFE_INTEGER`.
    */
   function getExpectedIndent (tokens) {
+    const trivial = isTrivialToken(tokens[0])
     let expectedIndent = Number.MAX_SAFE_INTEGER
 
     for (let i = 0; i < tokens.length; ++i) {
       const token = tokens[i]
       const offsetInfo = offsets.get(token)
 
-      if (offsetInfo != null) {
+      // If the first token is not trivial then ignore trivial following tokens.
+      if (offsetInfo != null && (trivial || !isTrivialToken(token))) {
         if (offsetInfo.expectedIndent != null) {
           expectedIndent = Math.min(expectedIndent, offsetInfo.expectedIndent)
         } else {
@@ -631,8 +648,16 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    */
   function validateCore (token, expectedIndent, optionalExpectedIndent) {
     const line = token.loc.start.line
-    const actualIndent = token.loc.start.column
     const indentText = getIndentText(token)
+
+    // If there is no line terminator after the `<script>` start tag,
+    // `indentText` contains non-whitespace characters.
+    // In that case, do nothing in order to prevent removing the `<script>` tag.
+    if (indentText.trim() !== '') {
+      return
+    }
+
+    const actualIndent = token.loc.start.column
     const unit = (options.indentChar === '\t' ? 'tab' : 'space')
 
     for (let i = 0; i < indentText.length; ++i) {
@@ -798,10 +823,12 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
 
     VElement (node) {
       const startTagToken = tokenStore.getFirstToken(node)
-      const childTokens = node.children.map(n => tokenStore.getFirstToken(n))
       const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
 
-      setOffset(childTokens, 1, startTagToken)
+      if (node.name !== 'pre') {
+        const childTokens = node.children.map(n => tokenStore.getFirstToken(n))
+        setOffset(childTokens, 1, startTagToken)
+      }
       setOffset(endTagToken, 0, startTagToken)
     },
 
@@ -1419,24 +1446,23 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     // Process semicolons.
     ':statement' (node) {
       const info = offsets.get(tokenStore.getFirstToken(node))
+      const lastToken = tokenStore.getLastToken(node)
       if (info == null) {
         return
       }
+      if (isSemicolon(lastToken)) {
+        offsets.set(lastToken, info)
+      }
 
       // Set to the semicolon of the previous token for semicolon-free style.
       // E.g.,
       //   foo
       //   ;[1,2,3].forEach(f)
-      const tokens = [
-        tokenStore.getTokenBefore(node),
-        tokenStore.getLastToken(node)
-      ].filter(isSemicolon)
-
-      // Set offsets if the semicolon is at beginning of line.
-      for (const token of tokens) {
-        const prevToken = tokenStore.getTokenBefore(token)
-        if (prevToken == null || token.loc.end.line !== prevToken.loc.start.line) {
-          offsets.set(token, info)
+      const prevToken = tokenStore.getTokenBefore(node)
+      if (isSemicolon(prevToken)) {
+        const prevPrevToken = tokenStore.getTokenBefore(prevToken)
+        if (prevPrevToken == null || prevToken.loc.end.line !== prevPrevToken.loc.start.line) {
+          offsets.set(prevToken, info)
         }
       }
     },
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 4667c437..677fff6b 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -384,12 +384,15 @@ module.exports = {
       callee.object.type === 'Identifier' &&
       callee.object.name === 'Vue' &&
       callee.property.type === 'Identifier' &&
-      (callee.property.name === 'component' || callee.property.name === 'mixin') &&
-      node.arguments.length &&
+      ['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 &&
+      node.arguments.length >= 1 &&
       node.arguments.slice(-1)[0].type === 'ObjectExpression'
 
-    const isDestructedVueComponent = callee.type === 'Identifier' &&
-      callee.name === 'component'
+    const isDestructedVueComponent = node.type === 'CallExpression' &&
+      callee.type === 'Identifier' &&
+      callee.name === 'component' &&
+      node.arguments.length >= 1 &&
+      node.arguments.slice(-1)[0].type === 'ObjectExpression'
 
     return isFullVueComponent || isDestructedVueComponent
   },
diff --git a/package.json b/package.json
index 990d591f..d469372b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-vue",
-  "version": "4.2.0",
+  "version": "4.2.2",
   "description": "Official ESLint plugin for Vue.js",
   "main": "lib/index.js",
   "scripts": {


Test patch:
diff --git a/tests/fixtures/html-indent/v-start-tag-02.vue b/tests/fixtures/html-indent/v-start-tag-02.vue
new file mode 100644
index 00000000..7ac1ee0d
--- /dev/null
+++ b/tests/fixtures/html-indent/v-start-tag-02.vue
@@ -0,0 +1,5 @@
+<!--{}-->
+<template>
+  <div
+    aaa>
+</template>
diff --git a/tests/fixtures/html-indent/v-start-tag-03.vue b/tests/fixtures/html-indent/v-start-tag-03.vue
new file mode 100644
index 00000000..4a4d918a
--- /dev/null
+++ b/tests/fixtures/html-indent/v-start-tag-03.vue
@@ -0,0 +1,6 @@
+<!--{}-->
+<template>
+  <div
+    aaa
+    bbb>
+</template>
diff --git a/tests/fixtures/html-indent/v-start-tag-04.vue b/tests/fixtures/html-indent/v-start-tag-04.vue
new file mode 100644
index 00000000..69ac40a5
--- /dev/null
+++ b/tests/fixtures/html-indent/v-start-tag-04.vue
@@ -0,0 +1,7 @@
+<!--{}-->
+<template>
+  <div
+    aaa
+    bbb
+  >
+</template>
diff --git a/tests/fixtures/html-indent/v-start-tag-05.vue b/tests/fixtures/html-indent/v-start-tag-05.vue
new file mode 100644
index 00000000..5d7d7155
--- /dev/null
+++ b/tests/fixtures/html-indent/v-start-tag-05.vue
@@ -0,0 +1,5 @@
+<!--{}-->
+<template>
+  <div
+    aaa/>
+</template>
diff --git a/tests/fixtures/script-indent/array-expression-02.vue b/tests/fixtures/script-indent/array-expression-02.vue
new file mode 100644
index 00000000..d398bde9
--- /dev/null
+++ b/tests/fixtures/script-indent/array-expression-02.vue
@@ -0,0 +1,5 @@
+<!--{}-->
+<script>
+var a = [
+  1]
+</script>
diff --git a/tests/fixtures/script-indent/function-declaration-04.vue b/tests/fixtures/script-indent/function-declaration-04.vue
new file mode 100644
index 00000000..d4ce7897
--- /dev/null
+++ b/tests/fixtures/script-indent/function-declaration-04.vue
@@ -0,0 +1,7 @@
+<!--{}-->
+<script>
+function foo(
+  a)
+{
+  foo()}
+</script>
diff --git a/tests/fixtures/script-indent/no-linebreak-script.vue b/tests/fixtures/script-indent/no-linebreak-script.vue
new file mode 100644
index 00000000..b8555f2d
--- /dev/null
+++ b/tests/fixtures/script-indent/no-linebreak-script.vue
@@ -0,0 +1,3 @@
+<!--{}-->
+<script>var a
+</script>
diff --git a/tests/fixtures/script-indent/object-expression-03.vue b/tests/fixtures/script-indent/object-expression-03.vue
new file mode 100644
index 00000000..f65063ec
--- /dev/null
+++ b/tests/fixtures/script-indent/object-expression-03.vue
@@ -0,0 +1,5 @@
+<!--{}-->
+<script>
+var obj = {
+  x: 1}
+</script>
diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js
new file mode 100644
index 00000000..39c20716
--- /dev/null
+++ b/tests/lib/rules/attributes-order.js
@@ -0,0 +1,302 @@
+/**
+ * @fileoverview enforce ordering of attributes
+ * @author Erin Depew
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/attributes-order')
+var RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var tester = new RuleTester({
+  parser: 'vue-eslint-parser',
+  parserOptions: { ecmaVersion: 2015 }
+})
+tester.run('attributes-order', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: '<template><div></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div is="header"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-for="item in items"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-if="!visible"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-else-if="!visible"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-else="!visible"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-show="!visible"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-cloak></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-pre></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-once></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div id="header"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div key="id"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-html="htmlContent"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-text="textContent"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-model="toggle"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code:
+      `<template>
+        <div
+          v-model="toggle"
+          :bindingProp="foo"
+          propOne="bar"
+          model="baz">
+        </div>
+      </template>`
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div click="functionCall"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div myProp="prop"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code:
+      `<template>
+          <div
+            is="header"
+            v-for="item in items"
+            v-if="!visible"
+            v-once
+            id="uniqueID"
+            ref="header"
+            v-model="headerData"
+            myProp="prop"
+            @click="functionCall"
+            v-text="textContent">
+          </div>
+        </template>`
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            is="header"
+            v-for="item in items"
+            v-if="!visible"
+            v-once
+            id="uniqueID"
+            ref="header"
+            v-model="headerData"
+            :myProp="prop"
+            v-on="functionCall"
+            v-text="textContent">
+          </div>
+        </template>`
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            is="header"
+            v-for="item in items"
+            v-if="!visible"
+            v-once
+            id="uniqueID"
+            ref="header"
+            :prop="headerData"
+            myProp="prop"
+            v-on:click="functionCall"
+            v-text="textContent">
+          </div>
+        </template>`
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            v-for="item in items"
+            v-if="!visible"
+            propone="prop"
+            proptwo="prop"
+            propthree="prop"
+            @click="functionCall"
+            v-text="textContent">
+          </div>
+        </template>`
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div propone="prop" proptwo="prop" propthree="prop"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div propone="prop" proptwo="prop" is="header"></div></template>',
+      options: [
+        { order:
+          ['LIST_RENDERING',
+            'CONDITIONALS',
+            'RENDER_MODIFIERS',
+            'GLOBAL',
+            'UNIQUE',
+            'BINDING',
+            'OTHER_ATTR',
+            'EVENTS',
+            'CONTENT',
+            'DEFINITION']
+        }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div ref="header" is="header" propone="prop" proptwo="prop"></div></template>',
+      options: [
+        { order:
+          ['LIST_RENDERING',
+            'CONDITIONALS',
+            'RENDER_MODIFIERS',
+            'GLOBAL',
+            'UNIQUE',
+            'BINDING',
+            'DEFINITION',
+            'OTHER_ATTR',
+            'EVENTS',
+            'CONTENT']
+        }]
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: '<template><div v-cloak is="header"></div></template>',
+      errors: [{
+        message: 'Attribute "is" should go before "v-cloak".',
+        type: 'VIdentifier'
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div id="uniqueID" v-cloak></div></template>',
+      errors: [{
+        message: 'Attribute "v-cloak" should go before "id".',
+        type: 'VDirectiveKey'
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            model="baz"
+            v-model="toggle"
+            propOne="bar"
+            :bindingProp="foo">
+          </div>
+        </template>`,
+      errors: [{
+        message: 'Attribute "v-model" should go before "model".',
+        type: 'VDirectiveKey'
+      },
+      {
+        message: 'Attribute ":bindingProp" should go before "propOne".',
+        type: 'VDirectiveKey'
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            :bindingProp="foo"
+            model="baz"
+            v-on="functionCall"
+            v-model="toggle"
+            propOne="bar">
+          </div>
+        </template>`,
+      errors: [{
+        message: 'Attribute "v-model" should go before "v-on".',
+        type: 'VDirectiveKey'
+      },
+      {
+        message: 'Attribute "propOne" should go before "v-on".',
+        type: 'VIdentifier'
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div data-id="foo" aria-test="bar" is="custom" myProp="prop"></div></template>',
+      errors: [{
+        message: 'Attribute "is" should go before "aria-test".',
+        type: 'VIdentifier'
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div ref="header" propone="prop" is="header" ></div></template>',
+      options: [
+        { order:
+          ['LIST_RENDERING',
+            'CONDITIONALS',
+            'RENDER_MODIFIERS',
+            'GLOBAL',
+            'UNIQUE',
+            'BINDING',
+            'DEFINITION',
+            'OTHER_ATTR',
+            'EVENTS',
+            'CONTENT']
+        }],
+      errors: [{
+        message: 'Attribute "is" should go before "propone".',
+        type: 'VIdentifier'
+      }]
+    }
+  ]
+})
diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js
index e97aae5f..b2f7ebe0 100644
--- a/tests/lib/rules/html-indent.js
+++ b/tests/lib/rules/html-indent.js
@@ -230,6 +230,19 @@ tester.run('html-indent', rule, loadPatterns(
         // Ignore all :D
         ignores: ['*']
       }]
+    },
+
+    // Pre
+    {
+      code: unIndent`
+        <template>
+          <pre>
+        aaa
+          bbb
+            ccc
+          </pre>
+        </template>
+      `
     }
   ],
 
@@ -480,6 +493,38 @@ tester.run('html-indent', rule, loadPatterns(
         { message: 'Expected indentation of 12 spaces but found 8 spaces.', line: 4 },
         { message: 'Expected indentation of 12 spaces but found 8 spaces.', line: 6 }
       ]
+    },
+
+    // Pre
+    {
+      code: unIndent`
+        <template>
+        <pre
+        style=""
+        >
+        aaa
+          bbb
+            ccc
+        </pre>
+        </template>
+      `,
+      output: unIndent`
+        <template>
+          <pre
+            style=""
+          >
+        aaa
+          bbb
+            ccc
+          </pre>
+        </template>
+      `,
+      errors: [
+        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
+        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
+        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 4 },
+        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 8 }
+      ]
     }
   ]
 ))
diff --git a/tests/lib/rules/max-attributes-per-line.js b/tests/lib/rules/max-attributes-per-line.js
index 773f1b66..0e8ab915 100644
--- a/tests/lib/rules/max-attributes-per-line.js
+++ b/tests/lib/rules/max-attributes-per-line.js
@@ -91,6 +91,8 @@ ruleTester.run('max-attributes-per-line', rule, {
   invalid: [
     {
       code: `<template><component name="John Doe" age="30"></component></template>`,
+      output: `<template><component name="John Doe" 
+age="30"></component></template>`,
       errors: ['Attribute "age" should be on a new line.']
     },
     {
@@ -99,6 +101,12 @@ ruleTester.run('max-attributes-per-line', rule, {
         age="30">
         </component>
       </template>`,
+      output: `<template><component 
+job="Vet"
+        name="John Doe"
+        age="30">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "job" should be on a new line.',
         type: 'VAttribute',
@@ -108,6 +116,8 @@ ruleTester.run('max-attributes-per-line', rule, {
     {
       code: `<template><component name="John Doe" age="30" job="Vet"></component></template>`,
       options: [{ singleline: { max: 2 }}],
+      output: `<template><component name="John Doe" age="30" 
+job="Vet"></component></template>`,
       errors: [{
         message: 'Attribute "job" should be on a new line.',
         type: 'VAttribute',
@@ -117,6 +127,8 @@ ruleTester.run('max-attributes-per-line', rule, {
     {
       code: `<template><component name="John Doe" age="30" job="Vet"></component></template>`,
       options: [{ singleline: 1, multiline: { max: 1, allowFirstLine: false }}],
+      output: `<template><component name="John Doe" 
+age="30" job="Vet"></component></template>`,
       errors: [{
         message: 'Attribute "age" should be on a new line.',
         type: 'VAttribute',
@@ -133,6 +145,11 @@ ruleTester.run('max-attributes-per-line', rule, {
         </component>
       </template>`,
       options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
+      output: `<template><component 
+name="John Doe"
+        age="30">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "name" should be on a new line.',
         type: 'VAttribute',
@@ -146,6 +163,12 @@ ruleTester.run('max-attributes-per-line', rule, {
         </component>
       </template>`,
       options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
+      output: `<template><component
+        name="John Doe" 
+age="30"
+        job="Vet">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "age" should be on a new line.',
         type: 'VAttribute',
@@ -159,6 +182,12 @@ ruleTester.run('max-attributes-per-line', rule, {
         </component>
       </template>`,
       options: [{ singleline: 3, multiline: 1 }],
+      output: `<template><component
+        name="John Doe" 
+age="30"
+        job="Vet">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "age" should be on a new line.',
         type: 'VAttribute',
@@ -172,6 +201,12 @@ ruleTester.run('max-attributes-per-line', rule, {
         </component>
       </template>`,
       options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
+      output: `<template><component
+        name="John Doe" age="30"
+        job="Vet" pet="dog" 
+petname="Snoopy">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "petname" should be on a new line.',
         type: 'VAttribute',
@@ -185,6 +220,12 @@ ruleTester.run('max-attributes-per-line', rule, {
         </component>
       </template>`,
       options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
+      output: `<template><component
+        name="John Doe" age="30"
+        job="Vet" pet="dog" 
+petname="Snoopy" extra="foo">
+        </component>
+      </template>`,
       errors: [{
         message: 'Attribute "petname" should be on a new line.',
         type: 'VAttribute',
diff --git a/tests/lib/rules/no-async-in-computed-properties.js b/tests/lib/rules/no-async-in-computed-properties.js
index e7cd0269..746ba319 100644
--- a/tests/lib/rules/no-async-in-computed-properties.js
+++ b/tests/lib/rules/no-async-in-computed-properties.js
@@ -60,6 +60,20 @@ ruleTester.run('no-async-in-computed-properties', rule, {
         }
       `,
       parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        async function resolveComponents(components) {
+          return await Promise.all(components.map(async (component) => {
+              if(typeof component === 'function') {
+                    return await component()
+                }
+                return component;
+          }));
+        }
+      `,
+      parserOptions: parserOptions8
     }
   ],
 
diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js
index 48501541..2eff407e 100644
--- a/tests/lib/rules/order-in-components.js
+++ b/tests/lib/rules/order-in-components.js
@@ -144,6 +144,19 @@ ruleTester.run('order-in-components', rule, {
         }
       `,
       parserOptions,
+      output: `
+        export default {
+          name: 'app',
+          props: {
+            propA: Number,
+          },
+          data () {
+            return {
+              msg: 'Welcome to Your Vue.js App'
+            }
+          },
+        }
+      `,
       errors: [{
         message: 'The "props" property should be above the "data" property on line 4.',
         line: 9
@@ -170,6 +183,24 @@ ruleTester.run('order-in-components', rule, {
         }
       `,
       parserOptions: { ecmaVersion: 6, sourceType: 'module', ecmaFeatures: { jsx: true }},
+      output: `
+        export default {
+          name: 'app',
+          render (h) {
+            return (
+              <span>{ this.msg }</span>
+            )
+          },
+          data () {
+            return {
+              msg: 'Welcome to Your Vue.js App'
+            }
+          },
+          props: {
+            propA: Number,
+          },
+        }
+      `,
       errors: [{
         message: 'The "name" property should be above the "render" property on line 3.',
         line: 8
@@ -196,6 +227,18 @@ ruleTester.run('order-in-components', rule, {
         })
       `,
       parserOptions: { ecmaVersion: 6 },
+      output: `
+        Vue.component('smart-list', {
+          name: 'app',
+          components: {},
+          data () {
+            return {
+              msg: 'Welcome to Your Vue.js App'
+            }
+          },
+          template: '<div></div>'
+        })
+      `,
       errors: [{
         message: 'The "components" property should be above the "data" property on line 4.',
         line: 9
@@ -217,6 +260,19 @@ ruleTester.run('order-in-components', rule, {
         })
       `,
       parserOptions: { ecmaVersion: 6 },
+      output: `
+        const { component } = Vue;
+        component('smart-list', {
+          name: 'app',
+          components: {},
+          data () {
+            return {
+              msg: 'Welcome to Your Vue.js App'
+            }
+          },
+          template: '<div></div>'
+        })
+      `,
       errors: [{
         message: 'The "components" property should be above the "data" property on line 5.',
         line: 10
@@ -238,6 +294,19 @@ ruleTester.run('order-in-components', rule, {
         })
       `,
       parserOptions: { ecmaVersion: 6 },
+      output: `
+        new Vue({
+          el: '#app',
+          name: 'app',
+          data () {
+            return {
+              msg: 'Welcome to Your Vue.js App'
+            }
+          },
+          components: {},
+          template: '<div></div>'
+        })
+      `,
       errors: [{
         message: 'The "el" property should be above the "name" property on line 3.',
         line: 4
@@ -267,6 +336,24 @@ ruleTester.run('order-in-components', rule, {
         };
       `,
       parserOptions,
+      output: `
+        export default {
+          name: 'burger',
+          data() {
+            return {
+              isActive: false,
+            };
+          },
+          methods: {
+            toggleMenu() {
+              this.isActive = !this.isActive;
+            },
+            closeMenu() {
+              this.isActive = false;
+            }
+          },
+        };
+      `,
       errors: [{
         message: 'The "name" property should be above the "data" property on line 3.',
         line: 16
@@ -283,11 +370,353 @@ ruleTester.run('order-in-components', rule, {
         };
       `,
       parserOptions,
+      output: `
+        export default {
+          data() {
+          },
+          test: 'ok',
+          name: 'burger',
+        };
+      `,
       options: [{ order: ['data', 'test', 'name'] }],
       errors: [{
         message: 'The "test" property should be above the "name" property on line 5.',
         line: 6
       }]
+    },
+    {
+      filename: 'example.vue',
+      code: `
+        export default {
+          /** data provider */
+          data() {
+          },
+          /** name of vue component */
+          name: 'burger'
+        };
+      `,
+      parserOptions,
+      output: `
+        export default {
+          /** name of vue component */
+          name: 'burger',
+          /** data provider */
+          data() {
+          },
+        };
+      `,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 4.',
+        line: 7
+      }]
+    },
+    {
+      filename: 'example.vue',
+      code: `export default {data(){},name:'burger'};`,
+      parserOptions,
+      output: `export default {name:'burger',data(){},};`,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 1.',
+        line: 1
+      }]
+    },
+    {
+      // side-effects CallExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: obj.fn(),
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects NewExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: new MyClass(),
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects UpdateExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: i++,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects AssignmentExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: i = 0,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects TaggedTemplateExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: template\`\${foo}\`,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects key
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          [obj.fn()]: 'test',
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects object deep props
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: {test: obj.fn()},
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects array elements
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: [obj.fn(), 1],
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects call at middle
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: obj.fn().prop,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects delete
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: delete obj.prop,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects within BinaryExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: fn() + a + b,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects within ConditionalExpression
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: a ? fn() : null,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // side-effects within TemplateLiteral
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          test: \`test \${fn()} \${a}\`,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: null,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 6
+      }]
+    },
+    {
+      // without side-effects
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          name: 'burger',
+          test: fn(),
+        };
+      `,
+      parserOptions,
+      output: `
+        export default {
+          name: 'burger',
+          data() {
+          },
+          test: fn(),
+        };
+      `,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 5
+      }]
+    },
+    {
+      // don't side-effects
+      filename: 'example.vue',
+      code: `
+        export default {
+          data() {
+          },
+          testArray: [1, 2, 3, true, false, 'a', 'b', 'c'],
+          testRegExp: /[a-z]*/,
+          testSpreadElement: [...array],
+          testOperator: (!!(a - b + c * d / e % f)) || (a && b),
+          testArrow: (a) => a,
+          testConditional: a ? b : c,
+          testYield: function* () {},
+          testTemplate: \`a:\${a},b:\${b},c:\${c}.\`,
+          name: 'burger',
+        };
+      `,
+      parserOptions,
+      output: `
+        export default {
+          name: 'burger',
+          data() {
+          },
+          testArray: [1, 2, 3, true, false, 'a', 'b', 'c'],
+          testRegExp: /[a-z]*/,
+          testSpreadElement: [...array],
+          testOperator: (!!(a - b + c * d / e % f)) || (a && b),
+          testArrow: (a) => a,
+          testConditional: a ? b : c,
+          testYield: function* () {},
+          testTemplate: \`a:\${a},b:\${b},c:\${c}.\`,
+        };
+      `,
+      errors: [{
+        message: 'The "name" property should be above the "data" property on line 3.',
+        line: 13
+      }]
     }
   ]
 })
diff --git a/tests/lib/rules/prop-name-casing.js b/tests/lib/rules/prop-name-casing.js
new file mode 100644
index 00000000..3df60c8b
--- /dev/null
+++ b/tests/lib/rules/prop-name-casing.js
@@ -0,0 +1,200 @@
+/**
+ * @fileoverview Define a style for the name property casing for consistency purposes
+ * @author Yu Kimura
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/prop-name-casing')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const parserOptions = {
+  ecmaVersion: 6,
+  sourceType: 'module',
+  ecmaFeatures: { experimentalObjectRestSpread: true }
+}
+
+const ruleTester = new RuleTester()
+ruleTester.run('prop-name-casing', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: ['greetingText']
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: some_props
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            ...some_props,
+          }
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: ['greetingText']
+        }
+      `,
+      options: ['camelCase'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: ['greetingText']
+        }
+      `,
+      options: ['snake_case'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            greetingText: String
+          }
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default {
+        props: {
+          greetingText: String
+        }
+      }
+      `,
+      options: ['camelCase'],
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default {
+        props: {
+          greeting_text: String
+        }
+      }
+      `,
+      options: ['snake_case'],
+      parserOptions
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            greeting_text: String
+          }
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: 'Prop "greeting_text" is not in camelCase.',
+        type: 'Property',
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            greeting_text: String
+          }
+        }
+      `,
+      options: ['camelCase'],
+      parserOptions,
+      errors: [{
+        message: 'Prop "greeting_text" is not in camelCase.',
+        type: 'Property',
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            greetingText: String
+          }
+        }
+      `,
+      options: ['snake_case'],
+      parserOptions,
+      errors: [{
+        message: 'Prop "greetingText" is not in snake_case.',
+        type: 'Property',
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            'greeting-text': String
+          }
+        }
+      `,
+      options: ['camelCase'],
+      parserOptions,
+      errors: [{
+        message: 'Prop "greeting-text" is not in camelCase.',
+        type: 'Property',
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: {
+            'greeting-text': String
+          }
+        }
+      `,
+      options: ['snake_case'],
+      parserOptions,
+      errors: [{
+        message: 'Prop "greeting-text" is not in snake_case.',
+        type: 'Property',
+        line: 4
+      }]
+    }
+  ]
+})
diff --git a/tests/lib/rules/valid-v-on.js b/tests/lib/rules/valid-v-on.js
index e7179c10..46dd8b4e 100644
--- a/tests/lib/rules/valid-v-on.js
+++ b/tests/lib/rules/valid-v-on.js
@@ -43,6 +43,30 @@ tester.run('valid-v-on', rule, {
       filename: 'test.vue',
       code: '<template><div @keydown.27="foo"></div></template>'
     },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.enter="foo"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.arrow-down="foo"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.esc="foo"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.a="foo"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.b="foo"></div></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div @keydown.a.b.c="foo"></div></template>'
+    },
     {
       filename: 'test.vue',
       code: '<template><el-from @submit.native.prevent></el-form></template>'
diff --git a/tests/lib/utils/casing.js b/tests/lib/utils/casing.js
index 743d24fd..e9cbe921 100644
--- a/tests/lib/utils/casing.js
+++ b/tests/lib/utils/casing.js
@@ -32,4 +32,13 @@ describe('getConverter()', () => {
     assert.ok(converter('FooBar') === 'foo-bar')
     assert.ok(converter('Foo1Bar') === 'foo1bar')
   })
+
+  it('should conver string to snake_case', () => {
+    const converter = casing.getConverter('snake_case')
+
+    assert.ok(converter('fooBar') === 'foo_bar')
+    assert.ok(converter('foo-bar') === 'foo_bar')
+    assert.ok(converter('FooBar') === 'foo_bar')
+    assert.ok(converter('Foo1Bar') === 'foo1bar')
+  })
 })
diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js
index 0dd2d0cf..87b8738a 100644
--- a/tests/lib/utils/vue-component.js
+++ b/tests/lib/utils/vue-component.js
@@ -126,6 +126,12 @@ function invalidTests (ext) {
       parserOptions,
       errors: [makeError(1)]
     },
+    {
+      filename: `test.${ext}`,
+      code: `Vue.extend({})`,
+      parserOptions,
+      errors: [makeError(1)]
+    },
     {
       filename: `test.${ext}`,
       code: `