Skip to content

Commit 85558dc

Browse files
filipalacerdamichalsnik
authored andcommitted
Add rule max-attributes-per-line (resolves #47) (#60)
* Inits new rule. Adds documentation. * Improves rule definition * Update docs to match review * Adds rule to check for number of attributes per line * Updates per code review: - Adds defaults values - Adds tests for the default values * Updates single line max attributes error as per code review * Changes schema to allow a different config Changes options parser to allow to redifine only one prop Changes error thrown for singleline errors Updates tests and docs accordingly * Moves function into utils Uses arrow function Improves readability * Fix eslint error * Improves code coverage * Update docs as per code review * reports the violated attributes instead of elements
1 parent df84d9b commit 85558dc

File tree

4 files changed

+483
-0
lines changed

4 files changed

+483
-0
lines changed

docs/rules/max-attributes-per-line.md

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Define the number of attributes allows per line (max-attributes-per-line)
2+
3+
Limits the maximum number of attributes/properties per line to improve readability.
4+
5+
6+
## :book: Rule Details
7+
8+
This rule aims to enforce a number of attributes per line in templates.
9+
It checks all the elements in a template and verifies that the number of attributes per line does not exceed the defined maximum.
10+
An attribute is considered to be in a new line when there is a line break between two attributes.
11+
12+
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).
13+
14+
:-1: Examples of **incorrect** code for this rule:
15+
16+
```html
17+
<component lorem="1" ipsum="2" dolor="3" sit="4">
18+
</component>
19+
20+
<component
21+
lorem="1" ipsum="2"
22+
dolor="3"
23+
sit="4"
24+
>
25+
</component>
26+
```
27+
28+
:+1: Examples of **correct** code for this rule:
29+
30+
```html
31+
<component lorem="1" ipsum="2" dolor="3">
32+
</component>
33+
34+
<component
35+
lorem="1"
36+
ipsum="2"
37+
dolor="3"
38+
sit="4"
39+
>
40+
</component>
41+
42+
```
43+
44+
### :wrench: Options
45+
46+
```
47+
{
48+
"vue/max-attributes-per-line": [{
49+
"singleline": 3,
50+
"multiline": {
51+
max: 1,
52+
allowFirstLine: false
53+
}
54+
}]
55+
}
56+
```
57+
58+
#### `allowFirstLine`
59+
For multi-line declarations, defines if allows attributes to be put in the first line. (Default false)
60+
61+
:-1: Example of **incorrect** code for this setting:
62+
```html
63+
// [{ "multiline": { "allowFirstLine": false }}]
64+
<component foo="John" bar="Smith"
65+
foobar={5555555}>
66+
</component>;
67+
```
68+
69+
:+1: Example of **correct** code for this setting:
70+
```html
71+
// [{ "multiline": { "allowFirstLine": false }}]
72+
<component
73+
foo="John"
74+
bar="Smith"
75+
foobar={5555555}
76+
>
77+
</component>;
78+
```
79+
80+
81+
#### `singleline`
82+
Number of maximum attributes per line when the opening tag is in a single line. (Default is 3)
83+
84+
:-1: Example of **incorrect** code for this setting:
85+
```html
86+
// [{"singleline": 2,}]
87+
<component foo="John" bar="Smith" foobar={5555555}></component>;
88+
```
89+
90+
:+1: Example of **correct** code for this setting:
91+
```html
92+
// [{"singleline": 3,}]
93+
<component foo="John" bar="Smith" foobar={5555555}></component>;
94+
```
95+
96+
97+
#### `multiline`
98+
Number of maximum attributes per line when a tag is in multiple lines. (Default is 1)
99+
100+
:-1: Example of **incorrect** code for this setting:
101+
```html
102+
// [{"multiline": 1}]
103+
<component foo="John" bar="Smith"
104+
foobar={5555555}>
105+
</component>;
106+
```
107+
108+
:+1: Example of **correct** code for this setting:
109+
```html
110+
// [{"multiline": 1}]
111+
<component
112+
foo="John"
113+
bar="Smith"
114+
foobar={5555555}
115+
>
116+
</component>;
117+
```
118+
119+
## When Not To Use It
120+
121+
If you do not want to check the number of attributes declared per line you can disable this rule.
122+

lib/rules/max-attributes-per-line.js

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* @fileoverview Define the number of attributes allows per line
3+
* @author Filipa Lacerda
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Rule Definition
9+
// ------------------------------------------------------------------------------
10+
const utils = require('../utils')
11+
12+
module.exports = {
13+
meta: {
14+
docs: {
15+
description: 'Define the number of attributes allows per line',
16+
category: 'Stylistic Issues',
17+
recommended: false
18+
},
19+
fixable: null,
20+
schema: [
21+
{
22+
type: 'object',
23+
properties: {
24+
singleline: {
25+
anyOf: [
26+
{
27+
type: 'number',
28+
minimum: 1
29+
},
30+
{
31+
type: 'object',
32+
properties: {
33+
max: {
34+
type: 'number',
35+
minimum: 1
36+
}
37+
},
38+
additionalProperties: false
39+
}
40+
]
41+
},
42+
multiline: {
43+
anyOf: [
44+
{
45+
type: 'number',
46+
minimum: 1
47+
},
48+
{
49+
type: 'object',
50+
properties: {
51+
max: {
52+
type: 'number',
53+
minimum: 1
54+
},
55+
allowFirstLine: {
56+
type: 'boolean'
57+
}
58+
},
59+
additionalProperties: false
60+
}
61+
]
62+
}
63+
}
64+
}
65+
]
66+
},
67+
68+
create: function (context) {
69+
const configuration = parseOptions(context.options[0])
70+
const multilineMaximum = configuration.multiline
71+
const singlelinemMaximum = configuration.singleline
72+
const canHaveFirstLine = configuration.allowFirstLine
73+
74+
utils.registerTemplateBodyVisitor(context, {
75+
'VStartTag' (node) {
76+
const numberOfAttributes = node.attributes.length
77+
78+
if (!numberOfAttributes) return
79+
80+
if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) {
81+
showErrors(node.attributes.slice(singlelinemMaximum), node)
82+
}
83+
84+
if (!utils.isSingleLine(node)) {
85+
if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) {
86+
showErrors([node.attributes[0]], node)
87+
}
88+
89+
groupAttrsByLine(node.attributes)
90+
.filter(attrs => attrs.length > multilineMaximum)
91+
.forEach(attrs => showErrors(attrs.splice(multilineMaximum), node))
92+
}
93+
}
94+
})
95+
96+
// ----------------------------------------------------------------------
97+
// Helpers
98+
// ----------------------------------------------------------------------
99+
function parseOptions (options) {
100+
const defaults = {
101+
singleline: 3,
102+
multiline: 1,
103+
allowFirstLine: false
104+
}
105+
106+
if (options) {
107+
if (typeof options.singleline === 'number') {
108+
defaults.singleline = options.singleline
109+
} else if (options.singleline && options.singleline.max) {
110+
defaults.singleline = options.singleline.max
111+
}
112+
113+
if (options.multiline) {
114+
if (typeof options.multiline === 'number') {
115+
defaults.multiline = options.multiline
116+
} else if (typeof options.multiline === 'object') {
117+
if (options.multiline.max) {
118+
defaults.multiline = options.multiline.max
119+
}
120+
121+
if (options.multiline.allowFirstLine) {
122+
defaults.allowFirstLine = options.multiline.allowFirstLine
123+
}
124+
}
125+
}
126+
}
127+
128+
return defaults
129+
}
130+
131+
function showErrors (attributes, node) {
132+
attributes.forEach((prop) => {
133+
context.report({
134+
node: prop,
135+
loc: prop.loc,
136+
message: 'Attribute "{{propName}}" should be on a new line.',
137+
data: {
138+
propName: prop.key.name
139+
}
140+
})
141+
})
142+
}
143+
144+
function groupAttrsByLine (attributes) {
145+
const propsPerLine = [[attributes[0]]]
146+
147+
attributes.reduce((previous, current) => {
148+
if (previous.loc.end.line === current.loc.start.line) {
149+
propsPerLine[propsPerLine.length - 1].push(current)
150+
} else {
151+
propsPerLine.push([current])
152+
}
153+
return current
154+
})
155+
156+
return propsPerLine
157+
}
158+
159+
return {}
160+
}
161+
}

lib/utils/index.js

+9
Original file line numberDiff line numberDiff line change
@@ -590,5 +590,14 @@ module.exports = {
590590
}
591591
}
592592
}
593+
},
594+
595+
/**
596+
* Check whether the component is declared in a single line or not.
597+
* @param {ASTNode} node
598+
* @returns {boolean}
599+
*/
600+
isSingleLine (node) {
601+
return node.loc.start.line === node.loc.end.line
593602
}
594603
}

0 commit comments

Comments
 (0)