diff --git a/docs/rules/README.md b/docs/rules/README.md
index e1c88ff56..428e8b752 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -146,6 +146,7 @@ For example:
| [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | |
| [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: |
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
+| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
| [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
| [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |
| [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: |
diff --git a/docs/rules/component-tags-order.md b/docs/rules/component-tags-order.md
new file mode 100644
index 000000000..d8c45447b
--- /dev/null
+++ b/docs/rules/component-tags-order.md
@@ -0,0 +1,96 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/component-tags-order
+description: enforce order of component top-level elements
+---
+# vue/component-tags-order
+> enforce order of component top-level elements
+
+## :book: Rule Details
+
+This rule warns about the order of the `
+...
+
+```
+
+
+
+
+
+```vue
+
+
+
+...
+```
+
+
+
+### `{ "order": ["template", "script", "style"] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+### `{ "order": ["docs", "template", "script", "style"] }`
+
+
+
+```vue
+
+ documents
+...
+
+
+```
+
+
+
+
+
+```vue
+
+...
+
+ documents
+
+```
+
+
+
+## :books: Further reading
+
+- [Style guide - Single-file component top-level element order](https://vuejs.org/v2/style-guide/#Single-file-component-top-level-element-order-recommended)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-tags-order.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-tags-order.js)
diff --git a/lib/index.js b/lib/index.js
index d2d3d0e15..e8aecf338 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -17,6 +17,7 @@ module.exports = {
'comma-dangle': require('./rules/comma-dangle'),
'comment-directive': require('./rules/comment-directive'),
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
+ 'component-tags-order': require('./rules/component-tags-order'),
'dot-location': require('./rules/dot-location'),
'eqeqeq': require('./rules/eqeqeq'),
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js
new file mode 100644
index 000000000..6f63c2cf0
--- /dev/null
+++ b/lib/rules/component-tags-order.js
@@ -0,0 +1,93 @@
+/**
+ * @author Yosuke Ota
+ * issue https://github.com/vuejs/eslint-plugin-vue/issues/140
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+const DEFAULT_ORDER = Object.freeze(['script', 'template', 'style'])
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce order of component top-level elements',
+ category: undefined,
+ url: 'https://eslint.vuejs.org/rules/component-tags-order.html'
+ },
+ fixable: null,
+ schema: {
+ type: 'array',
+ properties: {
+ order: {
+ type: 'array'
+ }
+ }
+ },
+ messages: {
+ unexpected: 'The <{{name}}> should be above the <{{firstUnorderedName}}> on line {{line}}.'
+ }
+ },
+ create (context) {
+ const order = (context.options[0] && context.options[0].order) || DEFAULT_ORDER
+ const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()
+
+ function getTopLevelHTMLElements () {
+ if (documentFragment) {
+ return documentFragment.children
+ }
+ return []
+ }
+
+ function report (element, firstUnorderedElement) {
+ context.report({
+ node: element,
+ loc: element.loc,
+ messageId: 'unexpected',
+ data: {
+ name: element.name,
+ firstUnorderedName: firstUnorderedElement.name,
+ line: firstUnorderedElement.loc.start.line
+ }
+ })
+ }
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {},
+ {
+ Program (node) {
+ if (utils.hasInvalidEOF(node)) {
+ return
+ }
+ const elements = getTopLevelHTMLElements()
+
+ elements.forEach((element, index) => {
+ const expectedIndex = order.indexOf(element.name)
+ if (expectedIndex < 0) {
+ return
+ }
+ const firstUnordered = elements
+ .slice(0, index)
+ .filter(e => expectedIndex < order.indexOf(e.name))
+ .sort(
+ (e1, e2) => order.indexOf(e1.name) - order.indexOf(e2.name)
+ )[0]
+ if (firstUnordered) {
+ report(element, firstUnordered)
+ }
+ })
+ }
+ }
+ )
+ }
+}
diff --git a/package.json b/package.json
index 7dabf02aa..19acf696c 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
"eslint": "^5.0.0 || ^6.0.0"
},
"dependencies": {
- "vue-eslint-parser": "^6.0.5"
+ "vue-eslint-parser": "^7.0.0"
},
"devDependencies": {
"@types/node": "^4.2.16",
diff --git a/tests/lib/rules/component-tags-order.js b/tests/lib/rules/component-tags-order.js
new file mode 100644
index 000000000..a9108fb76
--- /dev/null
+++ b/tests/lib/rules/component-tags-order.js
@@ -0,0 +1,217 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/component-tags-order')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser')
+})
+
+tester.run('component-tags-order', rule, {
+ valid: [
+ // default
+ '',
+ 'text
',
+ '',
+ '',
+ '',
+ `
+
+
+
+
+
+
+ `,
+
+ // order
+ {
+ code: '',
+ output: null,
+ options: [{ order: ['template', 'script', 'style'] }]
+ },
+ {
+ code: '',
+ output: null,
+ options: [{ order: ['style', 'template', 'script'] }]
+ },
+ {
+ code: '',
+ output: null,
+ options: [{ order: ['template', 'docs', 'script', 'style'] }]
+ },
+ {
+ code: '',
+ output: null,
+ options: [{ order: ['template', 'script', 'style'] }]
+ },
+ {
+ code: 'text
',
+ output: null,
+ options: [{ order: ['docs', 'script', 'template', 'style'] }]
+ },
+
+ ``,
+
+ // Invalid EOF
+ '