Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bf9b95c

Browse files
dev1437FloEdelmann
andauthored
Create padding-line-between-tags rule (#1966)
* Create space-between-siblings rule * Fix lint * Change how options are initialised * Fill in name * Remove block * Update test * Tidy up test and examples * Change message * Add test for flat tag * Add never functionality * Add tests for never and update previous tests for new schema * Linting * Update docs * Rename to padding-line-between-tags * Change schema to array of objects * Change messages * Allow for blank lines to be specified on each tag * Update tests * Linting * Update docs * Add another test * Lint * Clean up doc * Clean up tests * Change type * Fix doc * Update docs/rules/padding-line-between-tags.md Co-authored-by: Flo Edelmann <florian-edelmann@online.de> * Ignore top level * Remove testing stuff * Simplify logic and make last configuration apply * Add test for last configuration applying * Update docs * Add newlines between siblings on same line * Update docs * Lint * Fix doc * Fix spaces on line diff = 0 * Remove only space between tags * Append text backwards * Uncomment tests * Linting * Fix loop and add test * Add another test Co-authored-by: Flo Edelmann <florian-edelmann@online.de>
1 parent faa067e commit bf9b95c

File tree

5 files changed

+1386
-0
lines changed

5 files changed

+1386
-0
lines changed

‎docs/rules/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ For example:
258258
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | | :hammer: |
259259
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: | :lipstick: |
260260
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | | :hammer: |
261+
| [vue/padding-line-between-tags](./padding-line-between-tags.md) | Insert newlines between sibling tags in template | :wrench: | :warning: |
261262
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | :hammer: |
262263
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
263264
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: | :hammer: |
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/padding-line-between-tags
5+
description: Require or disallow newlines between sibling tags in template
6+
---
7+
# vue/padding-line-between-tags
8+
9+
> Require or disallow newlines between sibling tags in template
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
14+
## :book: Rule Details
15+
16+
This rule requires or disallows newlines between sibling HTML tags.
17+
18+
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error']}">
19+
20+
```vue
21+
<template>
22+
<div>
23+
<!-- ✓ GOOD: -->
24+
<div></div>
25+
26+
<div>
27+
</div>
28+
29+
<div />
30+
31+
<div />
32+
<!-- ✗ BAD: -->
33+
<div></div>
34+
<div>
35+
</div>
36+
<div /><div />
37+
</div>
38+
</template>
39+
```
40+
41+
</eslint-code-block>
42+
43+
## :wrench: Options
44+
45+
```json
46+
{
47+
"vue/padding-line-between-tags": ["error", [
48+
{ "blankLine": "always", "prev": "*", "next": "*" }
49+
]]
50+
}
51+
```
52+
53+
This rule requires blank lines between each sibling HTML tag by default.
54+
55+
A configuration is an object which has 3 properties; blankLine, prev and next. For example, { blankLine: "always", prev: "br", next: "div" } means "one or more blank lines are required between a br tag and a div tag." You can supply any number of configurations. If a tag pair matches multiple configurations, the last matched configuration will be used.
56+
57+
- `blankLine` is one of the following:
58+
- `always` requires one or more blank lines.
59+
- `never` disallows blank lines.
60+
- `prev` any tag name without brackets.
61+
- `next` any tag name without brackets.
62+
63+
### Disallow blank lines between all tags
64+
65+
`{ blankLine: 'never', prev: '*', next: '*' }`
66+
67+
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [
68+
{ blankLine: 'never', prev: '*', next: '*' }
69+
]]}">
70+
71+
```vue
72+
<template>
73+
<div>
74+
<div></div>
75+
<div>
76+
</div>
77+
<div />
78+
</div>
79+
</template>
80+
```
81+
82+
</eslint-code-block>
83+
84+
### Require newlines after `<br>`
85+
86+
`{ blankLine: 'always', prev: 'br', next: '*' }`
87+
88+
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [
89+
{ blankLine: 'always', prev: 'br', next: '*' }
90+
]]}">
91+
92+
```vue
93+
<template>
94+
<div>
95+
<ul>
96+
<li>
97+
</li>
98+
<br />
99+
100+
<li>
101+
</li>
102+
</ul>
103+
</div>
104+
</template>
105+
```
106+
107+
</eslint-code-block>
108+
109+
### Require newlines before `<br>`
110+
111+
`{ blankLine: 'always', prev: '*', next: 'br' }`
112+
113+
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [
114+
{ blankLine: 'always', prev: '*', next: 'br' }
115+
]]}">
116+
117+
```vue
118+
<template>
119+
<div>
120+
<ul>
121+
<li>
122+
</li>
123+
124+
<br />
125+
<li>
126+
</li>
127+
</ul>
128+
</div>
129+
</template>
130+
```
131+
132+
</eslint-code-block>
133+
134+
### Require newlines between `<br>` and `<img>`
135+
136+
`{ blankLine: 'always', prev: 'br', next: 'img' }`
137+
138+
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [
139+
{ blankLine: 'always', prev: 'br', next: 'img' }
140+
]]}">
141+
142+
```vue
143+
<template>
144+
<div>
145+
<ul>
146+
<li>
147+
</li>
148+
<br />
149+
150+
<img />
151+
<li>
152+
</li>
153+
</ul>
154+
</div>
155+
</template>
156+
```
157+
158+
</eslint-code-block>
159+
160+
## :mag: Implementation
161+
162+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-tags.js)
163+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-tags.js)

‎lib/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ module.exports = {
184184
'script-setup-uses-vars': require('./rules/script-setup-uses-vars'),
185185
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
186186
'sort-keys': require('./rules/sort-keys'),
187+
'padding-line-between-tags': require('./rules/padding-line-between-tags'),
187188
'space-in-parens': require('./rules/space-in-parens'),
188189
'space-infix-ops': require('./rules/space-infix-ops'),
189190
'space-unary-ops': require('./rules/space-unary-ops'),
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/**
2+
* @author dev1437
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
/**
14+
* Split the source code into multiple lines based on the line delimiters.
15+
* Copied from padding-line-between-blocks
16+
* @param {string} text Source code as a string.
17+
* @returns {string[]} Array of source code lines.
18+
*/
19+
function splitLines(text) {
20+
return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
21+
}
22+
23+
/**
24+
* @param {RuleContext} context
25+
* @param {VElement} tag
26+
* @param {VElement} sibling
27+
*/
28+
function insertNewLine(context, tag, sibling) {
29+
context.report({
30+
messageId: 'always',
31+
loc: sibling.loc,
32+
// @ts-ignore
33+
fix(fixer) {
34+
return fixer.insertTextAfter(tag, '\n')
35+
}
36+
})
37+
}
38+
39+
/**
40+
* @param {RuleContext} context
41+
* @param {VEndTag | VStartTag} endTag
42+
* @param {VElement} sibling
43+
*/
44+
function removeExcessLines(context, endTag, sibling) {
45+
context.report({
46+
messageId: 'never',
47+
loc: sibling.loc,
48+
// @ts-ignore
49+
fix(fixer) {
50+
const start = endTag.range[1]
51+
const end = sibling.range[0]
52+
const paddingText = context.getSourceCode().text.slice(start, end)
53+
const textBetween = splitLines(paddingText)
54+
let newTextBetween = `\n${textBetween.pop()}`
55+
for (let i = textBetween.length - 1; i >= 0; i--) {
56+
if (!/^\s*$/.test(textBetween[i])) {
57+
newTextBetween = `${i === 0 ? '' : '\n'}${
58+
textBetween[i]
59+
}${newTextBetween}`
60+
}
61+
}
62+
return fixer.replaceTextRange([start, end], `${newTextBetween}`)
63+
}
64+
})
65+
}
66+
67+
// ------------------------------------------------------------------------------
68+
// Rule Definition
69+
// ------------------------------------------------------------------------------
70+
71+
/**
72+
* @param {RuleContext} context
73+
*/
74+
function checkNewline(context) {
75+
/** @type {Array<{blankLine: "always" | "never", prev: string, next: string}>} */
76+
const configureList = context.options[0] || [
77+
{ blankLine: 'always', prev: '*', next: '*' }
78+
]
79+
80+
/**
81+
* @param {VElement} block
82+
*/
83+
return (block) => {
84+
if (!block.parent.parent) {
85+
return
86+
}
87+
88+
const endTag = block.endTag || block.startTag
89+
const lowerSiblings = block.parent.children
90+
.filter(
91+
(element) =>
92+
element.type === 'VElement' && element.range !== block.range
93+
)
94+
.filter((sibling) => sibling.range[0] - endTag.range[1] >= 0)
95+
96+
if (lowerSiblings.length === 0) {
97+
return
98+
}
99+
100+
const closestSibling = /** @type {VElement} */ (lowerSiblings[0])
101+
102+
for (let i = configureList.length - 1; i >= 0; --i) {
103+
const configure = configureList[i]
104+
const matched =
105+
(configure.prev === '*' || block.name === configure.prev) &&
106+
(configure.next === '*' || closestSibling.name === configure.next)
107+
108+
if (matched) {
109+
const lineDifference =
110+
closestSibling.loc.start.line - endTag.loc.end.line
111+
if (configure.blankLine === 'always') {
112+
if (lineDifference === 1) {
113+
insertNewLine(context, block, closestSibling)
114+
} else if (lineDifference === 0) {
115+
context.report({
116+
messageId: 'always',
117+
loc: closestSibling.loc,
118+
// @ts-ignore
119+
fix(fixer) {
120+
const lastSpaces = /** @type {RegExpExecArray} */ (
121+
/^\s*/.exec(
122+
context.getSourceCode().lines[endTag.loc.start.line - 1]
123+
)
124+
)[0]
125+
126+
return fixer.insertTextAfter(endTag, `\n\n${lastSpaces}`)
127+
}
128+
})
129+
}
130+
} else {
131+
if (lineDifference > 1) {
132+
let hasOnlyTextBetween = true
133+
for (
134+
let i = endTag.loc.start.line;
135+
i < closestSibling.loc.start.line - 1 && hasOnlyTextBetween;
136+
i++
137+
) {
138+
hasOnlyTextBetween = !/^\s*$/.test(
139+
context.getSourceCode().lines[i]
140+
)
141+
}
142+
if (!hasOnlyTextBetween) {
143+
removeExcessLines(context, endTag, closestSibling)
144+
}
145+
}
146+
}
147+
break
148+
}
149+
}
150+
}
151+
}
152+
153+
module.exports = {
154+
meta: {
155+
type: 'layout',
156+
docs: {
157+
description:
158+
'require or disallow newlines between sibling tags in template',
159+
categories: undefined,
160+
url: 'https://eslint.vuejs.org/rules/padding-line-between-tags.html'
161+
},
162+
fixable: 'whitespace',
163+
schema: [
164+
{
165+
type: 'array',
166+
items: {
167+
type: 'object',
168+
properties: {
169+
blankLine: { enum: ['always', 'never'] },
170+
prev: { type: 'string' },
171+
next: { type: 'string' }
172+
},
173+
additionalProperties: false,
174+
required: ['blankLine', 'prev', 'next']
175+
}
176+
}
177+
],
178+
messages: {
179+
never: 'Unexpected blank line before this tag.',
180+
always: 'Expected blank line before this tag.'
181+
}
182+
},
183+
/** @param {RuleContext} context */
184+
create(context) {
185+
return utils.defineTemplateBodyVisitor(context, {
186+
VElement: checkNewline(context)
187+
})
188+
}
189+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /