Repository: vuejs/eslint-plugin-vue
Pull request #393 (2018-02-16 03:41:21)

Docs: update README.md
This adds two things and removes one thing:

1. Clarifies the requirements for `eslint --fix` and `babel-eslint`.
2. Adds an FAQ entry for `eslint-plugin-html`.
3. Removes an FAQ entry for indentation since we have `vue/script-indent`.


Patch:
diff --git a/README.md b/README.md
index 26d89faf..1cc0c879 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ You can try this plugin on the Web.
 ## :grey_exclamation: Requirements
 
 - [ESLint](http://eslint.org/) `>=3.18.0`.
+  - `>=4.7.0` to use `eslint --fix`.
+  - `>=4.14.0` to use with `babel-eslint`.
 - Node.js `>=4.0.0`
 
 ## :cd: Installation
@@ -204,6 +206,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>` |
@@ -234,14 +237,20 @@ If you already use other parser (e.g. `"parser": "babel-eslint"`), please move i
 
 The `vue-eslint-parser` uses the parser which is set by `parserOptions.parser` to parse scripts.
 
-### Can my javascript code have increased indentation?
+### Why doesn't it work on .vue file?
 
-It depends on the version of eslint you're using.
+1. Make sure you don't have `eslint-plugin-html` in your config. The `eslint-plugin-html` extracts the content from `<script>` tags, but `eslint-vue-plugin` requires `<script>` tags and `<template>` tags in order to distinguish template and script in single file components.
 
-[indent](https://eslint.org/docs/rules/indent) rule in `eslint@3.x` makes it possible, but if you use `eslint@4.x` be aware that this rule has been rewritten and is more strict now, thus it doesn't allow to have increased initial indentation.
+  ```diff
+    "plugins": [
+      "vue",
+  -   "html"
+    ]
+  ```
 
-You can however use [indent-legacy](https://eslint.org/docs/rules/indent-legacy) rule instead.
-More informations [here](https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite).
+2. Make sure your tool is set to lint `.vue` files.
+  - CLI targets only `.js` files by default. You have to specify additional extensions by `--ext` option or glob patterns. E.g. `eslint "src/**/*.{js,vue}"` or `eslint src --ext .vue`.
+  - VSCode targets only JavaScript or HTML files by default. You have to add `{"autoFix": true, "language": "vue"}` into `eslint.validate` entry.
 
 ## :anchor: Semantic Versioning Policy
 
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/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/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/order-in-components.js b/lib/rules/order-in-components.js
index 6bd0fb22..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
 // ------------------------------------------------------------------------------
@@ -67,7 +137,7 @@ module.exports = {
       category: 'recommended',
       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..f744aee5
--- /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/valid-v-on.js b/lib/rules/valid-v-on.js
index 498fd54f..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
@@ -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/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 1b93f206..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 {
@@ -1429,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 4f03abd2..677fff6b 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -385,11 +385,14 @@ module.exports = {
       callee.object.name === 'Vue' &&
       callee.property.type === 'Identifier' &&
       ['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 &&
-      node.arguments.length &&
+      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 d469372b..12e8a947 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "eslint": "^3.18.0 || ^4.0.0"
   },
   "dependencies": {
-    "vue-eslint-parser": "^2.0.1"
+    "vue-eslint-parser": "^2.0.3"
   },
   "devDependencies": {
     "@types/node": "^4.2.16",


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/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/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/no-parsing-error.js b/tests/lib/rules/no-parsing-error.js
index cbb3962e..fcb4c351 100644
--- a/tests/lib/rules/no-parsing-error.js
+++ b/tests/lib/rules/no-parsing-error.js
@@ -206,7 +206,8 @@ tester.run('no-parsing-error', rule, {
       code: '<template><div xmlns=""></template>',
       options: [{ 'x-invalid-namespace': false }]
     },
-    '<template><div/></template>'
+    '<template><div/></template>',
+    '<template><div v-show="">hello</div></template>'
   ],
   invalid: [
     {
@@ -238,7 +239,7 @@ tester.run('no-parsing-error', rule, {
     },
     {
       filename: 'test.vue',
-      code: '<template><div v-show="">hello</div></template>',
+      code: '<template><div v-show=" ">hello</div></template>',
       errors: [
         { message: 'Parsing error: Expected to be an expression, but got empty.', column: 24 }
       ]
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/rules/valid-v-show.js b/tests/lib/rules/valid-v-show.js
index 03f7f769..31b4d03b 100644
--- a/tests/lib/rules/valid-v-show.js
+++ b/tests/lib/rules/valid-v-show.js
@@ -47,6 +47,11 @@ tester.run('valid-v-show', rule, {
       filename: 'test.vue',
       code: '<template><div v-show></div></template>',
       errors: ["'v-show' directives require that attribute value."]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div v-show=""></div></template>',
+      errors: ["'v-show' directives require that attribute value."]
     }
   ]
 })
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')
+  })
 })