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

Add vue/require-expose rule #1568

Merged
merged 1 commit into from Jul 17, 2021
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 @@ -325,6 +325,7 @@ For example:
| [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-emit-validator](./require-emit-validator.md) | require type definitions in emits | |
| [vue/require-expose](./require-expose.md) | require declare public properties using `expose` | |
| [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: |
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
Expand Down
120 changes: 120 additions & 0 deletions docs/rules/require-expose.md
@@ -0,0 +1,120 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/require-expose
description: require declare public properties using `expose`
---
# vue/require-expose

> require declare public properties using `expose`

- :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 enforces the component to explicitly declare the exposed properties to the component using `expose`. You can use `expose` to control the internal properties of a component so that they cannot be referenced externally.

The `expose` API was officially introduced in Vue 3.2.

<eslint-code-block :rules="{'vue/require-expose': ['error']}">

```vue
<script>
/* ✓ GOOD */
export default {
expose: ['increment'],
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/require-expose': ['error']}">

```vue
<script>
/* ✗ BAD */
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/require-expose': ['error']}">

```vue
<script>
/* ✓ GOOD */
import { ref } from 'vue'

export default {
setup(props, { expose }) {
const count = ref(0)

function increment() {
count.value++
}
// public
expose({
increment
})
// private
return { count }
}
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/require-expose': ['error']}">

```vue
<script>
/* ✗ BAD */
import { ref } from 'vue'

export default {
setup(props) {
const count = ref(0)

function increment() {
count.value++
}
return { increment, count }
}
}
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :books: Further Reading

- [Vue RFCs - 0042-expose-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0042-expose-api.md)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-expose.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-expose.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -145,6 +145,7 @@ module.exports = {
'require-direct-export': require('./rules/require-direct-export'),
'require-emit-validator': require('./rules/require-emit-validator'),
'require-explicit-emits': require('./rules/require-explicit-emits'),
'require-expose': require('./rules/require-expose'),
'require-name-property': require('./rules/require-name-property'),
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),
'require-prop-types': require('./rules/require-prop-types'),
Expand Down
47 changes: 12 additions & 35 deletions lib/rules/require-explicit-emits.js
Expand Up @@ -18,7 +18,12 @@
// Requirements
// ------------------------------------------------------------------------------

const { findVariable } = require('eslint-utils')
const {
findVariable,
isOpeningBraceToken,
isClosingBraceToken,
isOpeningBracketToken
} = require('eslint-utils')
const utils = require('../utils')
const { capitalize } = require('../utils/casing')

Expand Down Expand Up @@ -53,34 +58,6 @@ const FIX_EMITS_AFTER_OPTIONS = [
'renderTriggered',
'errorCaptured'
]

/**
* Check whether the given token is a left brace.
* @param {Token} token The token to check.
* @returns {boolean} `true` if the token is a left brace.
*/
function isLeftBrace(token) {
return token != null && token.type === 'Punctuator' && token.value === '{'
}

/**
* Check whether the given token is a right brace.
* @param {Token} token The token to check.
* @returns {boolean} `true` if the token is a right brace.
*/
function isRightBrace(token) {
return token != null && token.type === 'Punctuator' && token.value === '}'
}

/**
* Check whether the given token is a left bracket.
* @param {Token} token The token to check.
* @returns {boolean} `true` if the token is a left bracket.
*/
function isLeftBracket(token) {
return token != null && token.type === 'Punctuator' && token.value === '['
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -486,7 +463,7 @@ function buildSuggest(define, emits, nameNode, context) {
const emitsOptionValue = emitsOption.value
if (emitsOptionValue.type === 'ArrayExpression') {
const leftBracket = /** @type {Token} */ (
sourceCode.getFirstToken(emitsOptionValue, isLeftBracket)
sourceCode.getFirstToken(emitsOptionValue, isOpeningBracketToken)
)
return [
{
Expand All @@ -504,7 +481,7 @@ function buildSuggest(define, emits, nameNode, context) {
]
} else if (emitsOptionValue.type === 'ObjectExpression') {
const leftBrace = /** @type {Token} */ (
sourceCode.getFirstToken(emitsOptionValue, isLeftBrace)
sourceCode.getFirstToken(emitsOptionValue, isOpeningBraceToken)
)
return [
{
Expand Down Expand Up @@ -548,10 +525,10 @@ function buildSuggest(define, emits, nameNode, context) {
)
} else {
const objectLeftBrace = /** @type {Token} */ (
sourceCode.getFirstToken(object, isLeftBrace)
sourceCode.getFirstToken(object, isOpeningBraceToken)
)
const objectRightBrace = /** @type {Token} */ (
sourceCode.getLastToken(object, isRightBrace)
sourceCode.getLastToken(object, isClosingBraceToken)
)
return fixer.insertTextAfter(
objectLeftBrace,
Expand Down Expand Up @@ -583,10 +560,10 @@ function buildSuggest(define, emits, nameNode, context) {
)
} else {
const objectLeftBrace = /** @type {Token} */ (
sourceCode.getFirstToken(object, isLeftBrace)
sourceCode.getFirstToken(object, isOpeningBraceToken)
)
const objectRightBrace = /** @type {Token} */ (
sourceCode.getLastToken(object, isRightBrace)
sourceCode.getLastToken(object, isClosingBraceToken)
)
return fixer.insertTextAfter(
objectLeftBrace,
Expand Down