Skip to content

Commit 7c0cd3e

Browse files
authored
Add vue/no-restricted-block rule (#1389)
1 parent 01f7732 commit 7c0cd3e

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ For example:
301301
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | |
302302
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
303303
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
304+
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | |
304305
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | |
305306
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
306307
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | |

docs/rules/no-restricted-block.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-restricted-block
5+
description: disallow specific block
6+
---
7+
# vue/no-restricted-block
8+
9+
> disallow specific block
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+
13+
## :book: Rule Details
14+
15+
This rule allows you to specify block names that you don't want to use in your application.
16+
17+
## :wrench: Options
18+
19+
This rule takes a list of strings, where each string is a block name or pattern to be restricted:
20+
21+
```json
22+
{
23+
"vue/no-restricted-block": ["error", "style", "foo", "bar"]
24+
}
25+
```
26+
27+
<eslint-code-block :rules="{'vue/no-restricted-block': ['error', 'style', 'foo', 'bar']}">
28+
29+
```vue
30+
<!-- ✗ BAD -->
31+
<foo>
32+
Custom block
33+
</foo>
34+
<bar>
35+
Custom block
36+
</bar>
37+
<style>
38+
.foo {}
39+
</style>
40+
```
41+
42+
</eslint-code-block>
43+
44+
Alternatively, the rule also accepts objects.
45+
46+
```json
47+
{
48+
"vue/no-restricted-block": ["error",
49+
{
50+
"element": "style",
51+
"message": "Do not use <style> block in this project."
52+
},
53+
{
54+
"element": "foo",
55+
"message": "Do not use <foo> block in this project."
56+
},
57+
{
58+
"element": "/forbidden/",
59+
"message": "Do not use blocks that include `forbidden` in their name."
60+
}
61+
]
62+
}
63+
```
64+
65+
The following properties can be specified for the object.
66+
67+
- `element` ... Specify the block element name or pattern.
68+
- `message` ... Specify an optional custom message.
69+
70+
### `{ "element": "foo" }, { "element": "/forbidden/" }`
71+
72+
<eslint-code-block :rules="{'vue/no-restricted-block': ['error', { element: 'foo' }, { element: '/forbidden/' }]}">
73+
74+
```vue
75+
<!-- ✗ BAD -->
76+
<foo>
77+
✗ BAD
78+
</foo>
79+
<forbidden-block></forbidden-block>
80+
<block-forbidden></block-forbidden>
81+
```
82+
83+
</eslint-code-block>
84+
85+
## :mag: Implementation
86+
87+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-block.js)
88+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-block.js)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ module.exports = {
9191
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
9292
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
9393
'no-reserved-keys': require('./rules/no-reserved-keys'),
94+
'no-restricted-block': require('./rules/no-restricted-block'),
9495
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
9596
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
9697
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),

lib/rules/no-restricted-block.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const regexp = require('../utils/regexp')
9+
/**
10+
* @typedef {object} ParsedOption
11+
* @property { (block: VElement) => boolean } test
12+
* @property {string} [message]
13+
*/
14+
15+
/**
16+
* @param {string} str
17+
* @returns {(str: string) => boolean}
18+
*/
19+
function buildMatcher(str) {
20+
if (regexp.isRegExp(str)) {
21+
const re = regexp.toRegExp(str)
22+
return (s) => {
23+
re.lastIndex = 0
24+
return re.test(s)
25+
}
26+
}
27+
return (s) => s === str
28+
}
29+
/**
30+
* @param {any} option
31+
* @returns {ParsedOption}
32+
*/
33+
function parseOption(option) {
34+
if (typeof option === 'string') {
35+
const matcher = buildMatcher(option)
36+
return {
37+
test(block) {
38+
return matcher(block.rawName)
39+
}
40+
}
41+
}
42+
const parsed = parseOption(option.element)
43+
parsed.message = option.message
44+
return parsed
45+
}
46+
47+
module.exports = {
48+
meta: {
49+
type: 'suggestion',
50+
docs: {
51+
description: 'disallow specific block',
52+
categories: undefined,
53+
url: 'https://eslint.vuejs.org/rules/no-restricted-block.html'
54+
},
55+
fixable: null,
56+
schema: {
57+
type: 'array',
58+
items: {
59+
oneOf: [
60+
{ type: 'string' },
61+
{
62+
type: 'object',
63+
properties: {
64+
element: { type: 'string' },
65+
message: { type: 'string', minLength: 1 }
66+
},
67+
required: ['element'],
68+
additionalProperties: false
69+
}
70+
]
71+
},
72+
uniqueItems: true,
73+
minItems: 0
74+
},
75+
76+
messages: {
77+
// eslint-disable-next-line eslint-plugin/report-message-format
78+
restrictedBlock: '{{message}}'
79+
}
80+
},
81+
/** @param {RuleContext} context */
82+
create(context) {
83+
/** @type {ParsedOption[]} */
84+
const options = context.options.map(parseOption)
85+
86+
const documentFragment =
87+
context.parserServices.getDocumentFragment &&
88+
context.parserServices.getDocumentFragment()
89+
90+
function getTopLevelHTMLElements() {
91+
if (documentFragment) {
92+
return documentFragment.children.filter(utils.isVElement)
93+
}
94+
return []
95+
}
96+
97+
return {
98+
/** @param {Program} node */
99+
Program(node) {
100+
if (utils.hasInvalidEOF(node)) {
101+
return
102+
}
103+
for (const block of getTopLevelHTMLElements()) {
104+
for (const option of options) {
105+
if (option.test(block)) {
106+
const message = option.message || defaultMessage(block)
107+
context.report({
108+
node: block.startTag,
109+
messageId: 'restrictedBlock',
110+
data: { message }
111+
})
112+
break
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
/**
120+
* @param {VElement} block
121+
*/
122+
function defaultMessage(block) {
123+
return `Using \`<${block.rawName}>\` is not allowed.`
124+
}
125+
}
126+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
// ------------------------------------------------------------------------------
7+
// Requirements
8+
// ------------------------------------------------------------------------------
9+
10+
const RuleTester = require('eslint').RuleTester
11+
const rule = require('../../../lib/rules/no-restricted-block')
12+
13+
// ------------------------------------------------------------------------------
14+
// Tests
15+
// ------------------------------------------------------------------------------
16+
17+
const tester = new RuleTester({
18+
parser: require.resolve('vue-eslint-parser'),
19+
parserOptions: { ecmaVersion: 2020 }
20+
})
21+
22+
tester.run('no-restricted-block', rule, {
23+
valid: [
24+
{
25+
filename: 'test.vue',
26+
code: ''
27+
},
28+
{
29+
filename: 'test.vue',
30+
code: '<style>.foo {}</style>',
31+
options: ['foo']
32+
}
33+
],
34+
invalid: [
35+
{
36+
filename: 'test.vue',
37+
code: `<style>.foo {}</style><foo></foo>`,
38+
options: ['style', 'foo'],
39+
errors: [
40+
{
41+
message: 'Using `<style>` is not allowed.',
42+
line: 1,
43+
column: 1
44+
},
45+
{
46+
message: 'Using `<foo>` is not allowed.',
47+
line: 1,
48+
column: 23
49+
}
50+
]
51+
},
52+
{
53+
filename: 'test.vue',
54+
code: `<forbidden-block></forbidden-block>
55+
<block-forbidden></block-forbidden>`,
56+
options: ['/forbidden/'],
57+
errors: [
58+
'Using `<forbidden-block>` is not allowed.',
59+
'Using `<block-forbidden>` is not allowed.'
60+
]
61+
},
62+
{
63+
filename: 'test.vue',
64+
code: `<style>.foo {}</style>
65+
<forbidden-block></forbidden-block>
66+
<block-forbidden></block-forbidden>`,
67+
options: [
68+
{
69+
element: 'style',
70+
message: 'Do not use <style> block in this project.'
71+
},
72+
{
73+
element: '/forbidden/',
74+
message: 'Do not use blocks that include `forbidden` in their name.'
75+
}
76+
],
77+
errors: [
78+
{
79+
message: 'Do not use <style> block in this project.',
80+
line: 1,
81+
column: 1
82+
},
83+
{
84+
message: 'Do not use blocks that include `forbidden` in their name.',
85+
line: 2,
86+
column: 7
87+
},
88+
{
89+
message: 'Do not use blocks that include `forbidden` in their name.',
90+
line: 3,
91+
column: 7
92+
}
93+
]
94+
}
95+
]
96+
})

0 commit comments

Comments
 (0)