Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
1 parent
faa067e
commit bf9b95c
Showing
5 changed files
with
1,386 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
--- | ||
pageClass: rule-details | ||
sidebarDepth: 0 | ||
title: vue/padding-line-between-tags | ||
description: Require or disallow newlines between sibling tags in template | ||
--- | ||
# vue/padding-line-between-tags | ||
|
||
> Require or disallow newlines between sibling tags in template | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge> | ||
- :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. | ||
|
||
## :book: Rule Details | ||
|
||
This rule requires or disallows newlines between sibling HTML tags. | ||
|
||
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error']}"> | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<!-- ✓ GOOD: --> | ||
<div></div> | ||
<div> | ||
</div> | ||
<div /> | ||
<div /> | ||
<!-- ✗ BAD: --> | ||
<div></div> | ||
<div> | ||
</div> | ||
<div /><div /> | ||
</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :wrench: Options | ||
|
||
```json | ||
{ | ||
"vue/padding-line-between-tags": ["error", [ | ||
{ "blankLine": "always", "prev": "*", "next": "*" } | ||
]] | ||
} | ||
``` | ||
|
||
This rule requires blank lines between each sibling HTML tag by default. | ||
|
||
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. | ||
|
||
- `blankLine` is one of the following: | ||
- `always` requires one or more blank lines. | ||
- `never` disallows blank lines. | ||
- `prev` any tag name without brackets. | ||
- `next` any tag name without brackets. | ||
|
||
### Disallow blank lines between all tags | ||
|
||
`{ blankLine: 'never', prev: '*', next: '*' }` | ||
|
||
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [ | ||
{ blankLine: 'never', prev: '*', next: '*' } | ||
]]}"> | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<div></div> | ||
<div> | ||
</div> | ||
<div /> | ||
</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### Require newlines after `<br>` | ||
|
||
`{ blankLine: 'always', prev: 'br', next: '*' }` | ||
|
||
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [ | ||
{ blankLine: 'always', prev: 'br', next: '*' } | ||
]]}"> | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<ul> | ||
<li> | ||
</li> | ||
<br /> | ||
<li> | ||
</li> | ||
</ul> | ||
</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### Require newlines before `<br>` | ||
|
||
`{ blankLine: 'always', prev: '*', next: 'br' }` | ||
|
||
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [ | ||
{ blankLine: 'always', prev: '*', next: 'br' } | ||
]]}"> | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<ul> | ||
<li> | ||
</li> | ||
<br /> | ||
<li> | ||
</li> | ||
</ul> | ||
</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### Require newlines between `<br>` and `<img>` | ||
|
||
`{ blankLine: 'always', prev: 'br', next: 'img' }` | ||
|
||
<eslint-code-block fix :rules="{'vue/padding-line-between-tags': ['error', [ | ||
{ blankLine: 'always', prev: 'br', next: 'img' } | ||
]]}"> | ||
|
||
```vue | ||
<template> | ||
<div> | ||
<ul> | ||
<li> | ||
</li> | ||
<br /> | ||
<img /> | ||
<li> | ||
</li> | ||
</ul> | ||
</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-tags.js) | ||
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-tags.js) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* @author dev1437 | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const utils = require('../utils') | ||
|
||
/** | ||
* Split the source code into multiple lines based on the line delimiters. | ||
* Copied from padding-line-between-blocks | ||
* @param {string} text Source code as a string. | ||
* @returns {string[]} Array of source code lines. | ||
*/ | ||
function splitLines(text) { | ||
return text.split(/\r\n|[\r\n\u2028\u2029]/gu) | ||
} | ||
|
||
/** | ||
* @param {RuleContext} context | ||
* @param {VElement} tag | ||
* @param {VElement} sibling | ||
*/ | ||
function insertNewLine(context, tag, sibling) { | ||
context.report({ | ||
messageId: 'always', | ||
loc: sibling.loc, | ||
// @ts-ignore | ||
fix(fixer) { | ||
return fixer.insertTextAfter(tag, '\n') | ||
} | ||
}) | ||
} | ||
|
||
/** | ||
* @param {RuleContext} context | ||
* @param {VEndTag | VStartTag} endTag | ||
* @param {VElement} sibling | ||
*/ | ||
function removeExcessLines(context, endTag, sibling) { | ||
context.report({ | ||
messageId: 'never', | ||
loc: sibling.loc, | ||
// @ts-ignore | ||
fix(fixer) { | ||
const start = endTag.range[1] | ||
const end = sibling.range[0] | ||
const paddingText = context.getSourceCode().text.slice(start, end) | ||
const textBetween = splitLines(paddingText) | ||
let newTextBetween = `\n${textBetween.pop()}` | ||
for (let i = textBetween.length - 1; i >= 0; i--) { | ||
if (!/^\s*$/.test(textBetween[i])) { | ||
newTextBetween = `${i === 0 ? '' : '\n'}${ | ||
textBetween[i] | ||
}${newTextBetween}` | ||
} | ||
} | ||
return fixer.replaceTextRange([start, end], `${newTextBetween}`) | ||
} | ||
}) | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
/** | ||
* @param {RuleContext} context | ||
*/ | ||
function checkNewline(context) { | ||
/** @type {Array<{blankLine: "always" | "never", prev: string, next: string}>} */ | ||
const configureList = context.options[0] || [ | ||
{ blankLine: 'always', prev: '*', next: '*' } | ||
] | ||
|
||
/** | ||
* @param {VElement} block | ||
*/ | ||
return (block) => { | ||
if (!block.parent.parent) { | ||
return | ||
} | ||
|
||
const endTag = block.endTag || block.startTag | ||
const lowerSiblings = block.parent.children | ||
.filter( | ||
(element) => | ||
element.type === 'VElement' && element.range !== block.range | ||
) | ||
.filter((sibling) => sibling.range[0] - endTag.range[1] >= 0) | ||
|
||
if (lowerSiblings.length === 0) { | ||
return | ||
} | ||
|
||
const closestSibling = /** @type {VElement} */ (lowerSiblings[0]) | ||
|
||
for (let i = configureList.length - 1; i >= 0; --i) { | ||
const configure = configureList[i] | ||
const matched = | ||
(configure.prev === '*' || block.name === configure.prev) && | ||
(configure.next === '*' || closestSibling.name === configure.next) | ||
|
||
if (matched) { | ||
const lineDifference = | ||
closestSibling.loc.start.line - endTag.loc.end.line | ||
if (configure.blankLine === 'always') { | ||
if (lineDifference === 1) { | ||
insertNewLine(context, block, closestSibling) | ||
} else if (lineDifference === 0) { | ||
context.report({ | ||
messageId: 'always', | ||
loc: closestSibling.loc, | ||
// @ts-ignore | ||
fix(fixer) { | ||
const lastSpaces = /** @type {RegExpExecArray} */ ( | ||
/^\s*/.exec( | ||
context.getSourceCode().lines[endTag.loc.start.line - 1] | ||
) | ||
)[0] | ||
|
||
return fixer.insertTextAfter(endTag, `\n\n${lastSpaces}`) | ||
} | ||
}) | ||
} | ||
} else { | ||
if (lineDifference > 1) { | ||
let hasOnlyTextBetween = true | ||
for ( | ||
let i = endTag.loc.start.line; | ||
i < closestSibling.loc.start.line - 1 && hasOnlyTextBetween; | ||
i++ | ||
) { | ||
hasOnlyTextBetween = !/^\s*$/.test( | ||
context.getSourceCode().lines[i] | ||
) | ||
} | ||
if (!hasOnlyTextBetween) { | ||
removeExcessLines(context, endTag, closestSibling) | ||
} | ||
} | ||
} | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'layout', | ||
docs: { | ||
description: | ||
'require or disallow newlines between sibling tags in template', | ||
categories: undefined, | ||
url: 'https://eslint.vuejs.org/rules/padding-line-between-tags.html' | ||
}, | ||
fixable: 'whitespace', | ||
schema: [ | ||
{ | ||
type: 'array', | ||
items: { | ||
type: 'object', | ||
properties: { | ||
blankLine: { enum: ['always', 'never'] }, | ||
prev: { type: 'string' }, | ||
next: { type: 'string' } | ||
}, | ||
additionalProperties: false, | ||
required: ['blankLine', 'prev', 'next'] | ||
} | ||
} | ||
], | ||
messages: { | ||
never: 'Unexpected blank line before this tag.', | ||
always: 'Expected blank line before this tag.' | ||
} | ||
}, | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
return utils.defineTemplateBodyVisitor(context, { | ||
VElement: checkNewline(context) | ||
}) | ||
} | ||
} |
Oops, something went wrong.