diff --git a/docs/rules/max-attributes-per-line.md b/docs/rules/max-attributes-per-line.md
new file mode 100644
index 000000000..8493465b6
--- /dev/null
+++ b/docs/rules/max-attributes-per-line.md
@@ -0,0 +1,122 @@
+# Define the number of attributes allows per line (max-attributes-per-line)
+
+Limits the maximum number of attributes/properties per line to improve readability.
+
+
+## :book: Rule Details
+
+This rule aims to enforce a number of attributes per line in templates.
+It checks all the elements in a template and verifies that the number of attributes per line does not exceed the defined maximum.
+An attribute is considered to be in a new line when there is a line break between two attributes.
+
+There is a configurable number of attributes that are acceptable in one-line case (default 3), as well as how many attributes are acceptable per line in multi-line case (default 1).
+
+:-1: Examples of **incorrect** code for this rule:
+
+```html
+
+
+
+
+
+```
+
+:+1: Examples of **correct** code for this rule:
+
+```html
+
+
+
+
+
+
+```
+
+### :wrench: Options
+
+```
+{
+ "vue/max-attributes-per-line": [{
+ "singleline": 3,
+ "multiline": {
+ max: 1,
+ allowFirstLine: false
+ }
+ }]
+}
+```
+
+#### `allowFirstLine`
+For multi-line declarations, defines if allows attributes to be put in the first line. (Default false)
+
+:-1: Example of **incorrect** code for this setting:
+```html
+// [{ "multiline": { "allowFirstLine": false }}]
+
+;
+```
+
+:+1: Example of **correct** code for this setting:
+```html
+// [{ "multiline": { "allowFirstLine": false }}]
+
+;
+```
+
+
+#### `singleline`
+Number of maximum attributes per line when the opening tag is in a single line. (Default is 3)
+
+:-1: Example of **incorrect** code for this setting:
+```html
+// [{"singleline": 2,}]
+;
+```
+
+:+1: Example of **correct** code for this setting:
+```html
+// [{"singleline": 3,}]
+;
+```
+
+
+#### `multiline`
+Number of maximum attributes per line when a tag is in multiple lines. (Default is 1)
+
+:-1: Example of **incorrect** code for this setting:
+```html
+// [{"multiline": 1}]
+
+;
+```
+
+:+1: Example of **correct** code for this setting:
+```html
+// [{"multiline": 1}]
+
+;
+```
+
+## When Not To Use It
+
+If you do not want to check the number of attributes declared per line you can disable this rule.
+
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
new file mode 100644
index 000000000..f5309e4a1
--- /dev/null
+++ b/lib/rules/max-attributes-per-line.js
@@ -0,0 +1,161 @@
+/**
+ * @fileoverview Define the number of attributes allows per line
+ * @author Filipa Lacerda
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Define the number of attributes allows per line',
+ category: 'Stylistic Issues',
+ recommended: false
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ singleline: {
+ anyOf: [
+ {
+ type: 'number',
+ minimum: 1
+ },
+ {
+ type: 'object',
+ properties: {
+ max: {
+ type: 'number',
+ minimum: 1
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ },
+ multiline: {
+ anyOf: [
+ {
+ type: 'number',
+ minimum: 1
+ },
+ {
+ type: 'object',
+ properties: {
+ max: {
+ type: 'number',
+ minimum: 1
+ },
+ allowFirstLine: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+
+ create: function (context) {
+ const configuration = parseOptions(context.options[0])
+ const multilineMaximum = configuration.multiline
+ const singlelinemMaximum = configuration.singleline
+ const canHaveFirstLine = configuration.allowFirstLine
+
+ utils.registerTemplateBodyVisitor(context, {
+ 'VStartTag' (node) {
+ const numberOfAttributes = node.attributes.length
+
+ if (!numberOfAttributes) return
+
+ if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) {
+ showErrors(node.attributes.slice(singlelinemMaximum), node)
+ }
+
+ if (!utils.isSingleLine(node)) {
+ if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) {
+ showErrors([node.attributes[0]], node)
+ }
+
+ groupAttrsByLine(node.attributes)
+ .filter(attrs => attrs.length > multilineMaximum)
+ .forEach(attrs => showErrors(attrs.splice(multilineMaximum), node))
+ }
+ }
+ })
+
+ // ----------------------------------------------------------------------
+ // Helpers
+ // ----------------------------------------------------------------------
+ function parseOptions (options) {
+ const defaults = {
+ singleline: 3,
+ multiline: 1,
+ allowFirstLine: false
+ }
+
+ if (options) {
+ if (typeof options.singleline === 'number') {
+ defaults.singleline = options.singleline
+ } else if (options.singleline && options.singleline.max) {
+ defaults.singleline = options.singleline.max
+ }
+
+ if (options.multiline) {
+ if (typeof options.multiline === 'number') {
+ defaults.multiline = options.multiline
+ } else if (typeof options.multiline === 'object') {
+ if (options.multiline.max) {
+ defaults.multiline = options.multiline.max
+ }
+
+ if (options.multiline.allowFirstLine) {
+ defaults.allowFirstLine = options.multiline.allowFirstLine
+ }
+ }
+ }
+ }
+
+ return defaults
+ }
+
+ function showErrors (attributes, node) {
+ attributes.forEach((prop) => {
+ context.report({
+ node: prop,
+ loc: prop.loc,
+ message: 'Attribute "{{propName}}" should be on a new line.',
+ data: {
+ propName: prop.key.name
+ }
+ })
+ })
+ }
+
+ function groupAttrsByLine (attributes) {
+ const propsPerLine = [[attributes[0]]]
+
+ attributes.reduce((previous, current) => {
+ if (previous.loc.end.line === current.loc.start.line) {
+ propsPerLine[propsPerLine.length - 1].push(current)
+ } else {
+ propsPerLine.push([current])
+ }
+ return current
+ })
+
+ return propsPerLine
+ }
+
+ return {}
+ }
+}
diff --git a/lib/utils/index.js b/lib/utils/index.js
index b2a5bacbf..2487ddd9a 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -587,5 +587,14 @@ module.exports = {
}
}
}
+ },
+
+ /**
+ * Check whether the component is declared in a single line or not.
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+ isSingleLine (node) {
+ return node.loc.start.line === node.loc.end.line
}
}
diff --git a/tests/lib/rules/max-attributes-per-line.js b/tests/lib/rules/max-attributes-per-line.js
new file mode 100644
index 000000000..07d24197c
--- /dev/null
+++ b/tests/lib/rules/max-attributes-per-line.js
@@ -0,0 +1,191 @@
+/**
+ * @fileoverview Define the number of attributes allows per line
+ * @author Filipa Lacerda
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/max-attributes-per-line')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parser: 'vue-eslint-parser',
+ parserOptions: { ecmaVersion: 2015 }
+})
+
+ruleTester.run('max-attributes-per-line', rule, {
+ valid: [
+ {
+ code: ``
+ },
+ {
+ code: ``
+ },
+ {
+ code: ``,
+ options: [{ multiline: { allowFirstLine: true }}]
+ },
+ {
+ code: `
+
+ `
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 1 }]
+ },
+ {
+ code: ``,
+ options: [{ singleline: 1, multiline: { max: 1, allowFirstLine: false }}]
+ },
+ {
+ code: ``,
+ options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: true }}]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}]
+ }
+ ],
+
+ invalid: [
+ {
+ code: ``,
+ errors: ['Attribute "petname" should be on a new line.']
+ },
+ {
+ code: `
+
+ `,
+ errors: [{
+ message: 'Attribute "job" should be on a new line.',
+ type: 'VAttribute',
+ line: 1
+ }]
+ },
+ {
+ code: ``,
+ options: [{ singleline: { max: 1 }}],
+ errors: [{
+ message: 'Attribute "age" should be on a new line.',
+ type: 'VAttribute',
+ line: 1
+ }]
+ },
+ {
+ code: ``,
+ options: [{ singleline: 1, multiline: { max: 1, allowFirstLine: false }}],
+ errors: [{
+ message: 'Attribute "age" should be on a new line.',
+ type: 'VAttribute',
+ line: 1
+ }, {
+ message: 'Attribute "job" should be on a new line.',
+ type: 'VAttribute',
+ line: 1
+ }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
+ errors: [{
+ message: 'Attribute "name" should be on a new line.',
+ type: 'VAttribute',
+ line: 1
+ }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
+ errors: [{
+ message: 'Attribute "age" should be on a new line.',
+ type: 'VAttribute',
+ line: 2
+ }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: 1 }],
+ errors: [{
+ message: 'Attribute "age" should be on a new line.',
+ type: 'VAttribute',
+ line: 2
+ }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
+ errors: [{
+ message: 'Attribute "petname" should be on a new line.',
+ type: 'VAttribute',
+ line: 3
+ }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
+ errors: [{
+ message: 'Attribute "petname" should be on a new line.',
+ type: 'VAttribute',
+ line: 3
+ }, {
+ message: 'Attribute "extra" should be on a new line.',
+ type: 'VAttribute',
+ line: 3
+ }]
+ }
+ ]
+})