diff --git a/.eslintrc.js b/.eslintrc.js
index 4b8396bec..4b2731d67 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,7 +19,7 @@ module.exports = {
'eslint-plugin'
],
rules: {
- 'eslint-plugin/report-message-format': ['error', '^[A-Z`\'].*\\.$'],
+ 'eslint-plugin/report-message-format': ['error', '^[A-Z`\'{].*\\.$'],
'eslint-plugin/prefer-placeholders': 'error',
'eslint-plugin/consistent-output': 'error'
},
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
index 517a4ef89..421e55394 100644
--- a/lib/rules/max-attributes-per-line.js
+++ b/lib/rules/max-attributes-per-line.js
@@ -70,6 +70,7 @@ module.exports = {
const multilineMaximum = configuration.multiline
const singlelinemMaximum = configuration.singleline
const canHaveFirstLine = configuration.allowFirstLine
+ const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
return utils.defineTemplateBodyVisitor(context, {
'VStartTag' (node) {
@@ -78,17 +79,17 @@ module.exports = {
if (!numberOfAttributes) return
if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) {
- showErrors(node.attributes.slice(singlelinemMaximum), node)
+ showErrors(node.attributes.slice(singlelinemMaximum))
}
if (!utils.isSingleLine(node)) {
if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) {
- showErrors([node.attributes[0]], node)
+ showErrors([node.attributes[0]])
}
groupAttrsByLine(node.attributes)
.filter(attrs => attrs.length > multilineMaximum)
- .forEach(attrs => showErrors(attrs.splice(multilineMaximum), node))
+ .forEach(attrs => showErrors(attrs.splice(multilineMaximum)))
}
}
})
@@ -128,16 +129,45 @@ module.exports = {
return defaults
}
- function showErrors (attributes, node) {
+ function getPropData (prop) {
+ let propType = 'Attribute'
+ let propName = prop.key.name
+
+ if (utils.isBindingAttribute(prop)) {
+ propType = 'Binding'
+ propName = prop.key.raw.argument
+ } else if (utils.isEventAttribute(prop)) {
+ propType = 'Event'
+ propName = prop.key.raw.argument
+ } else if (prop.directive) {
+ propType = 'Directive'
+ }
+
+ return { propType, propName }
+ }
+
+ function showErrors (attributes) {
attributes.forEach((prop, i) => {
+ const fix = (fixer) => {
+ if (i !== 0) return null
+
+ // Find the closest token before the current prop
+ // that is not a white space
+ const prevToken = template.getTokenBefore(prop, {
+ filter: (token) => token.type !== 'HTMLWhitespace'
+ })
+
+ const range = [prevToken.range[1], prop.range[0]]
+
+ return fixer.replaceTextRange(range, '\n')
+ }
+
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
+ message: '{{propType}} "{{propName}}" should be on a new line.',
+ data: getPropData(prop),
+ fix
})
})
}
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 28caa444d..bc9cc0407 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -294,6 +294,26 @@ module.exports = {
return VOID_ELEMENT_NAMES.has(name)
},
+ /**
+ * Check whether the given attribute node is a binding
+ * @param {ASTNode} name The attribute to check.
+ * @returns {boolean}
+ */
+ isBindingAttribute (attribute) {
+ return attribute.directive &&
+ attribute.key.name === 'bind' &&
+ attribute.key.argument
+ },
+
+ /**
+ * Check whether the given attribute node is an event
+ * @param {ASTNode} name The attribute to check.
+ * @returns {boolean}
+ */
+ isEventAttribute (attribute) {
+ return attribute.directive && attribute.key.name === 'on'
+ },
+
/**
* Parse member expression node to get array with all of its parts
* @param {ASTNode} MemberExpression
diff --git a/tests/lib/rules/max-attributes-per-line.js b/tests/lib/rules/max-attributes-per-line.js
index 0e8ab915f..ce4d45ffe 100644
--- a/tests/lib/rules/max-attributes-per-line.js
+++ b/tests/lib/rules/max-attributes-per-line.js
@@ -91,17 +91,53 @@ ruleTester.run('max-attributes-per-line', rule, {
invalid: [
{
code: ``,
- output: ``,
errors: ['Attribute "age" should be on a new line.']
},
+ {
+ code: ``,
+ output: ``,
+ errors: ['Binding "age" should be on a new line.']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ['Directive "bind" should be on a new line.']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ['Event "buy" should be on a new line.']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ['Event "click" should be on a new line.']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ['Directive "if" should be on a new line.']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ['Binding "age" should be on a new line.']
+ },
{
code: `
`,
- output: `
@@ -116,7 +152,7 @@ job="Vet"
{
code: ``,
options: [{ singleline: { max: 2 }}],
- output: ``,
errors: [{
message: 'Attribute "job" should be on a new line.',
@@ -127,7 +163,7 @@ job="Vet">`,
{
code: ``,
options: [{ singleline: 1, multiline: { max: 1, allowFirstLine: false }}],
- output: ``,
errors: [{
message: 'Attribute "age" should be on a new line.',
@@ -145,7 +181,7 @@ age="30" job="Vet">`,
`,
options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
- output: `
@@ -164,7 +200,7 @@ name="John Doe"
`,
options: [{ singleline: 3, multiline: { max: 1, allowFirstLine: false }}],
output: `
@@ -183,7 +219,7 @@ age="30"
`,
options: [{ singleline: 3, multiline: 1 }],
output: `
@@ -203,7 +239,7 @@ age="30"
options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
output: `
`,
@@ -222,7 +258,7 @@ petname="Snoopy">
options: [{ singleline: 3, multiline: { max: 2, allowFirstLine: false }}],
output: `
`,