Skip to content

Add rule max-attributes-per-line (resolves #47) #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Aug 15, 2017
Merged
122 changes: 122 additions & 0 deletions docs/rules/max-attributes-per-line.md
Original file line number Diff line number Diff line change
@@ -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
<component lorem="1" ipsum="2" dolor="3" sit="4">
</component>

<component
lorem="1" ipsum="2"
dolor="3"
sit="4"
>
</component>
```

:+1: Examples of **correct** code for this rule:

```html
<component lorem="1" ipsum="2" dolor="3">
</component>

<component
lorem="1"
ipsum="2"
dolor="3"
sit="4"
>
</component>

```

### :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 }}]
<component foo="John" bar="Smith"
foobar={5555555}>
</component>;
```

:+1: Example of **correct** code for this setting:
```html
// [{ "multiline": { "allowFirstLine": false }}]
<component
foo="John"
bar="Smith"
foobar={5555555}
>
</component>;
```


#### `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,}]
<component foo="John" bar="Smith" foobar={5555555}></component>;
```

:+1: Example of **correct** code for this setting:
```html
// [{"singleline": 3,}]
<component foo="John" bar="Smith" foobar={5555555}></component>;
```


#### `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}]
<component foo="John" bar="Smith"
foobar={5555555}>
</component>;
```

:+1: Example of **correct** code for this setting:
```html
// [{"multiline": 1}]
<component
foo="John"
bar="Smith"
foobar={5555555}
>
</component>;
```

## When Not To Use It

If you do not want to check the number of attributes declared per line you can disable this rule.

161 changes: 161 additions & 0 deletions lib/rules/max-attributes-per-line.js
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
9 changes: 9 additions & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Loading