Skip to content
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

New: Add vue/padding-line-between-blocks rule #1021

Merged
merged 1 commit into from Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -165,6 +165,7 @@ For example:
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
Expand Down
139 changes: 139 additions & 0 deletions docs/rules/padding-line-between-blocks.md
@@ -0,0 +1,139 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/padding-line-between-blocks
description: require or disallow padding lines between blocks
---
# vue/padding-line-between-blocks
> require or disallow padding lines between blocks

- :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 blank lines between the given 2 blocks. Properly blank lines help developers to understand the code.

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error']}">

```vue
<!-- ✓ GOOD -->
<template>
<div></div>
</template>

<script>
export default {}
</script>

<style></style>
```

</eslint-code-block>

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error']}">

```vue
<!-- ✗ BAD -->
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style></style>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/padding-line-between-blocks": ["error", "always" | "never"]
}
```

- `"always"` (default) ... Requires one or more blank lines. Note it does not count lines that comments exist as blank lines.
- `"never"` ... Disallows blank lines.

### `"always"` (default)

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error']}">

```vue
<!-- ✓ GOOD -->
<template>
<div></div>
</template>

<script>
export default {}
</script>

<style></style>
```

</eslint-code-block>

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error']}">

```vue
<!-- ✗ BAD -->
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style></style>
```

</eslint-code-block>

### `"never"`

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error', 'never']}">

```vue
<!-- ✓ GOOD -->
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style></style>
```

</eslint-code-block>

<eslint-code-block fix :rules="{'vue/padding-line-between-blocks': ['error', 'never']}">

```vue
<!-- ✗ BAD -->
<template>
<div></div>
</template>

<script>
export default {}
</script>

<style></style>
```

</eslint-code-block>

## :books: Further reading

- [padding-line-between-statements]
- [lines-between-class-members]

[padding-line-between-statements]: https://eslint.org/docs/rules/padding-line-between-statements
[lines-between-class-members]: https://eslint.org/docs/rules/lines-between-class-members


## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-blocks.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-blocks.js)
1 change: 1 addition & 0 deletions lib/configs/no-layout-rules.js
Expand Up @@ -25,6 +25,7 @@ module.exports = {
'vue/no-multi-spaces': 'off',
'vue/no-spaces-around-equal-signs-in-attribute': 'off',
'vue/object-curly-spacing': 'off',
'vue/padding-line-between-blocks': 'off',
'vue/script-indent': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/space-infix-ops': 'off',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -65,6 +65,7 @@ module.exports = {
'no-v-html': require('./rules/no-v-html'),
'object-curly-spacing': require('./rules/object-curly-spacing'),
'order-in-components': require('./rules/order-in-components'),
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'prop-name-casing': require('./rules/prop-name-casing'),
'require-component-is': require('./rules/require-component-is'),
'require-default-prop': require('./rules/require-default-prop'),
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/component-tags-order.js
Expand Up @@ -45,7 +45,7 @@ module.exports = {

function getTopLevelHTMLElements () {
if (documentFragment) {
return documentFragment.children
return documentFragment.children.filter(e => e.type === 'VElement')
}
return []
}
Expand Down
194 changes: 194 additions & 0 deletions lib/rules/padding-line-between-blocks.js
@@ -0,0 +1,194 @@
/**
* @fileoverview Require or disallow padding lines between blocks
* @author Yosuke Ota
*/
'use strict'
const utils = require('../utils')

/**
* Split the source code into multiple lines based on the line delimiters.
* @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)
}

/**
* Check and report blocks for `never` configuration.
* This autofix removes blank lines between the given 2 blocks.
* @param {RuleContext} context The rule context to report.
* @param {VElement} prevBlock The previous block to check.
* @param {VElement} nextBlock The next block to check.
* @param {Token[]} betweenTokens The array of tokens between blocks.
* @returns {void}
* @private
*/
function verifyForNever (context, prevBlock, nextBlock, betweenTokens) {
if (prevBlock.loc.end.line === nextBlock.loc.start.line) {
// same line
return
}
const tokenOrNodes = [...betweenTokens, nextBlock]
let prev = prevBlock
const paddingLines = []
for (const tokenOrNode of tokenOrNodes) {
const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
if (numOfLineBreaks > 1) {
paddingLines.push([prev, tokenOrNode])
}
prev = tokenOrNode
}
if (!paddingLines.length) {
return
}

context.report({
node: nextBlock,
messageId: 'never',
fix (fixer) {
return paddingLines.map(([prevToken, nextToken]) => {
const start = prevToken.range[1]
const end = nextToken.range[0]
const paddingText = context.getSourceCode().text
.slice(start, end)
const lastSpaces = splitLines(paddingText).pop()
return fixer.replaceTextRange([start, end], '\n' + lastSpaces)
})
}
})
}

/**
* Check and report blocks for `always` configuration.
* This autofix inserts a blank line between the given 2 blocks.
* @param {RuleContext} context The rule context to report.
* @param {VElement} prevBlock The previous block to check.
* @param {VElement} nextBlock The next block to check.
* @param {Token[]} betweenTokens The array of tokens between blocks.
* @returns {void}
* @private
*/
function verifyForAlways (context, prevBlock, nextBlock, betweenTokens) {
const tokenOrNodes = [...betweenTokens, nextBlock]
let prev = prevBlock
let linebreak
for (const tokenOrNode of tokenOrNodes) {
const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
if (numOfLineBreaks > 1) {
// Already padded.
return
}
if (!linebreak && numOfLineBreaks > 0) {
linebreak = prev
}
prev = tokenOrNode
}

context.report({
node: nextBlock,
messageId: 'always',
fix (fixer) {
if (linebreak) {
return fixer.insertTextAfter(linebreak, '\n')
}
return fixer.insertTextAfter(prevBlock, '\n\n')
}
})
}

/**
* Types of blank lines.
* `never` and `always` are defined.
* Those have `verify` method to check and report statements.
* @private
*/
const PaddingTypes = {
never: { verify: verifyForNever },
always: { verify: verifyForAlways }
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'layout',
docs: {
description: 'require or disallow padding lines between blocks',
category: undefined,
url: 'https://eslint.vuejs.org/rules/padding-line-between-blocks.html'
},
fixable: 'whitespace',
schema: [
{
enum: Object.keys(PaddingTypes)
}
],
messages: {
never: 'Unexpected blank line before this block.',
always: 'Expected blank line before this block.'
}
},
create (context) {
const paddingType = PaddingTypes[context.options[0] || 'always']
const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()

let tokens
function getTopLevelHTMLElements () {
if (documentFragment) {
return documentFragment.children.filter(e => e.type === 'VElement')
}
return []
}

function getTokenAndCommentsBetween (prev, next) {
// When there is no <template>, tokenStore.getTokensBetween cannot be used.
if (!tokens) {
tokens = [
...documentFragment.tokens
.filter(token => token.type !== 'HTMLWhitespace'),
...documentFragment.comments
].sort((a, b) => a.range[0] > b.range[0] ? 1 : a.range[0] < b.range[0] ? -1 : 0)
}

let token = tokens.shift()

const results = []
while (token) {
if (prev.range[1] <= token.range[0]) {
if (next.range[0] <= token.range[0]) {
tokens.unshift(token)
break
} else {
results.push(token)
}
}
token = tokens.shift()
}

return results
}

return utils.defineTemplateBodyVisitor(
context,
{},
{
Program (node) {
if (utils.hasInvalidEOF(node)) {
return
}
const elements = [...getTopLevelHTMLElements()]

let prev = elements.shift()
for (const element of elements) {
const betweenTokens = getTokenAndCommentsBetween(prev, element)
paddingType.verify(context, prev, element, betweenTokens)
prev = element
}
}
}
)
}
}