Skip to content

Commit

Permalink
Add vue/no-restricted-block rule (#1389)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Dec 27, 2020
1 parent 01f7732 commit 7c0cd3e
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -301,6 +301,7 @@ For example:
| [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 | |
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | |
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | |
Expand Down
88 changes: 88 additions & 0 deletions docs/rules/no-restricted-block.md
@@ -0,0 +1,88 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-restricted-block
description: disallow specific block
---
# vue/no-restricted-block

> disallow specific block
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :book: Rule Details

This rule allows you to specify block names that you don't want to use in your application.

## :wrench: Options

This rule takes a list of strings, where each string is a block name or pattern to be restricted:

```json
{
"vue/no-restricted-block": ["error", "style", "foo", "bar"]
}
```

<eslint-code-block :rules="{'vue/no-restricted-block': ['error', 'style', 'foo', 'bar']}">

```vue
<!-- ✗ BAD -->
<foo>
Custom block
</foo>
<bar>
Custom block
</bar>
<style>
.foo {}
</style>
```

</eslint-code-block>

Alternatively, the rule also accepts objects.

```json
{
"vue/no-restricted-block": ["error",
{
"element": "style",
"message": "Do not use <style> block in this project."
},
{
"element": "foo",
"message": "Do not use <foo> block in this project."
},
{
"element": "/forbidden/",
"message": "Do not use blocks that include `forbidden` in their name."
}
]
}
```

The following properties can be specified for the object.

- `element` ... Specify the block element name or pattern.
- `message` ... Specify an optional custom message.

### `{ "element": "foo" }, { "element": "/forbidden/" }`

<eslint-code-block :rules="{'vue/no-restricted-block': ['error', { element: 'foo' }, { element: '/forbidden/' }]}">

```vue
<!-- ✗ BAD -->
<foo>
✗ BAD
</foo>
<forbidden-block></forbidden-block>
<block-forbidden></block-forbidden>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-block.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-block.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -91,6 +91,7 @@ module.exports = {
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
'no-restricted-block': require('./rules/no-restricted-block'),
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
Expand Down
126 changes: 126 additions & 0 deletions lib/rules/no-restricted-block.js
@@ -0,0 +1,126 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const regexp = require('../utils/regexp')
/**
* @typedef {object} ParsedOption
* @property { (block: VElement) => boolean } test
* @property {string} [message]
*/

/**
* @param {string} str
* @returns {(str: string) => boolean}
*/
function buildMatcher(str) {
if (regexp.isRegExp(str)) {
const re = regexp.toRegExp(str)
return (s) => {
re.lastIndex = 0
return re.test(s)
}
}
return (s) => s === str
}
/**
* @param {any} option
* @returns {ParsedOption}
*/
function parseOption(option) {
if (typeof option === 'string') {
const matcher = buildMatcher(option)
return {
test(block) {
return matcher(block.rawName)
}
}
}
const parsed = parseOption(option.element)
parsed.message = option.message
return parsed
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow specific block',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-restricted-block.html'
},
fixable: null,
schema: {
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: {
element: { type: 'string' },
message: { type: 'string', minLength: 1 }
},
required: ['element'],
additionalProperties: false
}
]
},
uniqueItems: true,
minItems: 0
},

messages: {
// eslint-disable-next-line eslint-plugin/report-message-format
restrictedBlock: '{{message}}'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {ParsedOption[]} */
const options = context.options.map(parseOption)

const documentFragment =
context.parserServices.getDocumentFragment &&
context.parserServices.getDocumentFragment()

function getTopLevelHTMLElements() {
if (documentFragment) {
return documentFragment.children.filter(utils.isVElement)
}
return []
}

return {
/** @param {Program} node */
Program(node) {
if (utils.hasInvalidEOF(node)) {
return
}
for (const block of getTopLevelHTMLElements()) {
for (const option of options) {
if (option.test(block)) {
const message = option.message || defaultMessage(block)
context.report({
node: block.startTag,
messageId: 'restrictedBlock',
data: { message }
})
break
}
}
}
}
}

/**
* @param {VElement} block
*/
function defaultMessage(block) {
return `Using \`<${block.rawName}>\` is not allowed.`
}
}
}
96 changes: 96 additions & 0 deletions tests/lib/rules/no-restricted-block.js
@@ -0,0 +1,96 @@
/**
* @author Yosuke Ota
*/
'use strict'

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

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

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

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

tester.run('no-restricted-block', rule, {
valid: [
{
filename: 'test.vue',
code: ''
},
{
filename: 'test.vue',
code: '<style>.foo {}</style>',
options: ['foo']
}
],
invalid: [
{
filename: 'test.vue',
code: `<style>.foo {}</style><foo></foo>`,
options: ['style', 'foo'],
errors: [
{
message: 'Using `<style>` is not allowed.',
line: 1,
column: 1
},
{
message: 'Using `<foo>` is not allowed.',
line: 1,
column: 23
}
]
},
{
filename: 'test.vue',
code: `<forbidden-block></forbidden-block>
<block-forbidden></block-forbidden>`,
options: ['/forbidden/'],
errors: [
'Using `<forbidden-block>` is not allowed.',
'Using `<block-forbidden>` is not allowed.'
]
},
{
filename: 'test.vue',
code: `<style>.foo {}</style>
<forbidden-block></forbidden-block>
<block-forbidden></block-forbidden>`,
options: [
{
element: 'style',
message: 'Do not use <style> block in this project.'
},
{
element: '/forbidden/',
message: 'Do not use blocks that include `forbidden` in their name.'
}
],
errors: [
{
message: 'Do not use <style> block in this project.',
line: 1,
column: 1
},
{
message: 'Do not use blocks that include `forbidden` in their name.',
line: 2,
column: 7
},
{
message: 'Do not use blocks that include `forbidden` in their name.',
line: 3,
column: 7
}
]
}
]
})

0 comments on commit 7c0cd3e

Please sign in to comment.