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

feat: Add no-empty-component-block rule #1222

Merged
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -286,6 +286,7 @@ For example:
| [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` | |
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | |
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | |
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | |
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
Expand Down
68 changes: 68 additions & 0 deletions docs/rules/no-empty-component-block.md
@@ -0,0 +1,68 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-empty-component-block
description: disallow the `<template>` `<script>` `<style>` block to be empty
---
# vue/no-empty-component-block
> disallow the `<template>` `<script>` `<style>` block to be empty

## :book: Rule Details

This rule disallows the `<template>` `<script>` `<style>` block to be empty.

This rule also checks block what has attribute `src`.
See: https://vue-loader.vuejs.org/spec.html#src-imports

<eslint-code-block :rules="{'vue/no-empty-component-block': ['error']}">

```vue
// ✓ GOOD
<template>
<p>foo</p>
</template>

<script>
console.log('foo')
</script>

<style>
p {
display: inline;
}
</style>

<template src="./template.html"></template>
<template src="./template.html" />

<script src="./script.js"></script>
<script src="./script.js" />

<style src="./style.css"></style>
<style src="./style.css" />


// ✗ BAD
<template></template>
<template />
<template src="" />

<script></script>
<script />
<script src="" />

<style></style>
<style />
<style src="" />
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-empty-component-block.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-empty-component-block.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -69,6 +69,7 @@ module.exports = {
'no-dupe-keys': require('./rules/no-dupe-keys'),
'no-duplicate-attr-inheritance': require('./rules/no-duplicate-attr-inheritance'),
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
'no-empty-component-block': require('./rules/no-empty-component-block'),
'no-empty-pattern': require('./rules/no-empty-pattern'),
'no-extra-parens': require('./rules/no-extra-parens'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
Expand Down
99 changes: 99 additions & 0 deletions lib/rules/no-empty-component-block.js
@@ -0,0 +1,99 @@
/**
* @author tyankatsu <https://github.com/tyankatsu0105>
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

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

/**
* check whether has attribute `src`
*/
function hasAttributeSrc(componentBlock) {
const hasAttribute = componentBlock.startTag.attributes.length > 0

const hasSrc =
componentBlock.startTag.attributes.filter(
(attribute) =>
attribute.key.name === 'src' && attribute.value.value !== ''
).length > 0

return hasAttribute && hasSrc
}

/**
* check whether value under the component block is only whitespaces or break lines
*/
function isValueOnlyWhiteSpacesOrLineBreaks(componentBlock) {
return (
componentBlock.children.length === 1 &&
componentBlock.children[0].type === 'VText' &&
!componentBlock.children[0].value.trim()
)
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'disallow the `<template>` `<script>` `<style>` block to be empty',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-empty-component-block.html'
},
fixable: null,
schema: [],
messages: {
unexpected: '`<{{ blockName }}>` is empty. Empty block is not allowed.'
}
},

/**
* @param {RuleContext} context - The rule context.
* @returns {RuleListener} AST event handlers.
*/
create(context) {
if (!context.parserServices.getDocumentFragment) {
return {}
}

const componentBlocks = context.parserServices.getDocumentFragment()
.children

return {
Program(node) {
for (const componentBlock of componentBlocks) {
if (
componentBlock.name !== 'template' &&
componentBlock.name !== 'script' &&
componentBlock.name !== 'style'
)
continue

// https://vue-loader.vuejs.org/spec.html#src-imports
if (hasAttributeSrc(componentBlock)) continue

if (
isValueOnlyWhiteSpacesOrLineBreaks(componentBlock) ||
componentBlock.children.length === 0
) {
context.report({
node: componentBlock,
loc: componentBlock.loc,
messageId: 'unexpected',
data: {
blockName: componentBlock.name
}
})
}
}
}
}
}
}
145 changes: 145 additions & 0 deletions tests/lib/rules/no-empty-component-block.js
@@ -0,0 +1,145 @@
/**
* @author tyankatsu <https://github.com/tyankatsu0105>
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-empty-component-block')

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2018 }
})

tester.run('no-empty-component-block', rule, {
valid: [
`<template><p>foo</p></template>`,
`<template> foobar </template>`,
`<template><p>foo</p></template><script>console.log('foo')</script>`,
`<template><p>foo</p></template><script>console.log('foo')</script><style>p{display: inline;}</style>`,
`<template src="./template.html"></template>`,
`<template src="./template.html" />`,
`<template src="./template.html"></template><script src="./script.js"></script>`,
`<template src="./template.html" /><script src="./script.js" />`,
`<template src="./template.html"></template><script src="./script.js"></script><style src="./style.css"></style>`,
`<template src="./template.html" /><script src="./script.js" /><style src="./style.css" />`
],
invalid: [
{
code: `<template></template>`,
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
}
]
},
{
code: `<template> </template>`,
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
}
]
},
{
code: `<template>
</template>`,
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template src="" />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template></template><script></script>',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template /><script />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template src="" /><script src="" />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template></template><script></script><style></style>',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add testcases that contains whitespaces such as line breaks? It probably won't report because it contains VText.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. Good point!
I'll fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some invalid cases.

errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
},
{
message: '`<style>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template /><script /><style />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
},
{
message: '`<style>` is empty. Empty block is not allowed.'
}
]
},
{
code: '<template src="" /><script src="" /><style src="" />',
errors: [
{
message: '`<template>` is empty. Empty block is not allowed.'
},
{
message: '`<script>` is empty. Empty block is not allowed.'
},
{
message: '`<style>` is empty. Empty block is not allowed.'
}
]
}
]
})