Skip to content

Commit

Permalink
Add vue/no-restricted-component-names rule (#2210)
Browse files Browse the repository at this point in the history
  • Loading branch information
ItMaga authored Jun 12, 2023
1 parent 81ce0ce commit e643d44
Show file tree
Hide file tree
Showing 5 changed files with 510 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ For example:
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | | :hammer: |
| [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components | | :warning: |
| [vue/no-restricted-component-names](./no-restricted-component-names.md) | disallow specific component names | :bulb: | :hammer: |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | | :hammer: |
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | :bulb: | :hammer: |
| [vue/no-restricted-html-elements](./no-restricted-html-elements.md) | disallow specific HTML elements | | :hammer: |
Expand Down
89 changes: 89 additions & 0 deletions docs/rules/no-restricted-component-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-restricted-component-names
description: disallow specific component names
---
# vue/no-restricted-component-names

> disallow specific component names
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

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

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

```vue
<!-- ✗ BAD -->
<script>
export default {
name: 'Disallow',
}
</script>
```

</eslint-code-block>

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

```vue
<!-- ✓ GOOD -->
<script>
export default {
name: 'Allow',
}
</script>
```

</eslint-code-block>

## :wrench: Options

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

```json
{
"vue/no-restricted-component-names": ["error", "foo", "/^Disallow/"]
}
```

Alternatively, you can specify an object with a `name` property and an optional `message` and `suggest` property:

```json
{
"vue/no-restricted-component-names": [
"error",
{
"name": "Disallow",
"message": "Please do not use `Disallow` as a component name",
"suggest": "allow"
},
{
"name": "/^custom/",
"message": "Please do not use component names starting with 'custom'"
}
]
}
```

<eslint-code-block :rules="{'vue/no-restricted-component-names': ['error', { name: 'Disallow', message: 'Please do not use \'Disallow\' as a component name', suggest: 'allow'}]}">

```vue
<!-- ✗ BAD -->
<script>
export default {
name: 'Disallow',
}
</script>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-component-names.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-component-names.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ module.exports = {
'no-restricted-block': require('./rules/no-restricted-block'),
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
'no-restricted-class': require('./rules/no-restricted-class'),
'no-restricted-component-names': require('./rules/no-restricted-component-names'),
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
'no-restricted-html-elements': require('./rules/no-restricted-html-elements'),
Expand Down
157 changes: 157 additions & 0 deletions lib/rules/no-restricted-component-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* @author ItMaga <https://github.com/ItMaga>
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')
const { isRegExp, toRegExp } = require('../utils/regexp')

/**
* @typedef {object} OptionParsed
* @property { (name: string) => boolean } test
* @property {string|undefined} [message]
* @property {string|undefined} [suggest]
*/

/**
* @param {string} str
* @returns {(str: string) => boolean}
* @private
*/
function buildMatcher(str) {
if (isRegExp(str)) {
const regex = toRegExp(str)
return (s) => regex.test(s)
}
return (s) => s === casing.pascalCase(str) || s === casing.kebabCase(str)
}

/**
* @param {string|{name: string, message?: string, suggest?: string}} option
* @returns {OptionParsed}
* @private
* */
function parseOption(option) {
if (typeof option === 'string') {
const matcher = buildMatcher(option)
return { test: matcher }
}
const parsed = parseOption(option.name)
parsed.message = option.message
parsed.suggest = option.suggest
return parsed
}

/**
* @param {Property | AssignmentProperty} property
* @param {string | undefined} suggest
* @returns {Rule.SuggestionReportDescriptor[]}
* @private
* */
function createSuggest(property, suggest) {
if (!suggest) {
return []
}

return [
{
fix(fixer) {
return fixer.replaceText(property.value, JSON.stringify(suggest))
},
messageId: 'suggest',
data: { suggest }
}
]
}

module.exports = {
meta: {
hasSuggestions: true,
type: 'suggestion',
docs: {
description: 'disallow specific component names',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-restricted-component-names.html'
},
fixable: null,
schema: {
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: {
name: { type: 'string' },
message: { type: 'string', minLength: 1 },
suggest: { type: 'string' }
},
required: ['name'],
additionalProperties: false
}
]
},
uniqueItems: true,
minItems: 0
},
messages: {
// eslint-disable-next-line eslint-plugin/report-message-format
disallow: '{{message}}',
suggest: 'Instead, change to `{{suggest}}`.'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {OptionParsed[]} */
const options = context.options.map(parseOption)

/**
* @param {ObjectExpression} node
*/
function verify(node) {
const property = utils.findProperty(node, 'name')
if (!property) return

const propertyName = utils.getStaticPropertyName(property)
if (propertyName === 'name' && property.value.type === 'Literal') {
const componentName = property.value.value?.toString()
if (!componentName) {
return
}

for (const option of options) {
if (option.test(componentName)) {
context.report({
node: property.value,
messageId: 'disallow',
data: {
message:
option.message ||
`Using component name \`${componentName}\` is not allowed.`
},
suggest: createSuggest(property, option.suggest)
})
}
}
}
}

return utils.compositingVisitors(
utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
verify(node)
}
}),
utils.defineScriptSetupVisitor(context, {
onDefineOptionsEnter(node) {
const expression = node.arguments[0]
if (expression.type === 'ObjectExpression') {
verify(expression)
}
}
})
)
}
}
Loading

0 comments on commit e643d44

Please sign in to comment.