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(no-this-in-before-router-enter): create rule #1506

72 changes: 72 additions & 0 deletions docs/rules/no-this-in-before-route-enter.md
@@ -0,0 +1,72 @@
# vue/no-this-in-before-route-enter

> This rule prevents usage this in the "beforeRouteEnter" because this is undefined there. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

## Rule Details

Copy link
Member

@ota-meshi ota-meshi Jun 3, 2021

Choose a reason for hiding this comment

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

Could you write a description of the rule here? Documents should automatically update their headers and footers with the npm run update command.

Bad:
Copy link
Member

Choose a reason for hiding this comment

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

Use <eslint-code-block> like any other docs to build your docs for online demos. You can try it locally with the npm run docs:watch command.

```js
Copy link
Member

Choose a reason for hiding this comment

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

Should be html or vue.

<script>
export default {
beforeRouteEnter() {
this.method(); // Uncaught TypeError: Cannot read property 'method' of undefined
}
}
</script>
```

Bad:
```js
<script>
export default {
beforeRouteEnter() {
this.attribute = 42;
}
}
</script>
```

Bad:
```js
<script>
export default {
beforeRouteEnter() {
if (this.value === 42) {

}
}
}
</script>
```

Good:
```js
<script>
export default {
beforeRouteEnter() {
// anything without this
}
}
</script>
```

### Options

If there are any options, describe them here. Otherwise, delete this section.
przemyslawjanpietrzak marked this conversation as resolved.
Show resolved Hide resolved

## When Not To Use It

Give a short description of when it would be appropriate to turn off this rule.

Copy link
Member

Choose a reason for hiding this comment

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

If there is no content, delete it.

Suggested change
## When Not To Use It
Give a short description of when it would be appropriate to turn off this rule.

## Further Reading

[vue-router - in-component-guards](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards)

## :rocket: Version

This rule was introduced in eslint-plugin-vue 7.11.0

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-this-in-before-route-enter.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-this-in-before-route-enter.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -113,6 +113,7 @@ module.exports = {
'no-template-shadow': require('./rules/no-template-shadow'),
'no-template-target-blank': require('./rules/no-template-target-blank'),
'no-textarea-mustache': require('./rules/no-textarea-mustache'),
'no-this-in-before-route': require('./rules/no-this-in-before-route-enter'),
'no-unregistered-components': require('./rules/no-unregistered-components'),
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
Expand Down
95 changes: 95 additions & 0 deletions lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,95 @@
/**
* @fileoverview Don't use this in a beforeRouteEnter method
* @author Przemyslaw Jan Beigert
*/
'use strict'

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

const utils = require('../utils')

;('use strict')
/**
* @param {FunctionExpression | BlockStatement | Property | ThisExpression} obj
* @returns {boolean}
*/
function deepFindThisExpression(obj) {
if (typeof obj !== 'object' || !obj) {
return false
}
if (obj.type === 'ThisExpression') {
return true
}

/** @param {typeof obj} key */
return Object.entries(obj).some(([key, value]) => {
if (key === 'parent') {
return false
}
if (Array.isArray(value)) {
return value.some((item) => deepFindThisExpression(item))
}
if (typeof value === 'object') {
return deepFindThisExpression(value)
}

return false
})
}

/**
* @param {Property | SpreadElement} property
* @returns {property is Property}
*/
function isPropertyBeforeRouteMethod(property) {
if (property.type !== 'Property') {
return false
}

return (
property.key.type === 'Identifier' &&
property.key.name === 'beforeRouteEnter'
)
}

const errorMessage =
'beforeRouteEnter does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards'

module.exports = {
errorMessage,
meta: {
type: 'suggestion',
docs: {
description: 'disallow this usage in a beforeRouteEnter method',
categories: null,
url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html'
},
schema: []
},

/** @param {RuleContext} context */
create(context) {
// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------
return utils.executeOnVue(context, (obj) => {
const beforeRouteProperty = obj.properties.find(
isPropertyBeforeRouteMethod
)
if (!beforeRouteProperty) {
return
}
if (beforeRouteProperty.value.type !== 'FunctionExpression') {
return
}
if (deepFindThisExpression(beforeRouteProperty.value.body)) {
context.report({
node: beforeRouteProperty,
message: errorMessage
})
}
})
}
}
przemyslawjanpietrzak marked this conversation as resolved.
Show resolved Hide resolved
144 changes: 144 additions & 0 deletions tests/lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,144 @@
/**
* @fileoverview Don&#39;t use &#34;this&#34; i a beforeRouteEnter method
* @author Przemyslaw Jan Beigert
*/
'use strict'

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

const rule = require('../../../lib/rules/no-this-in-before-route-enter')
const RuleTester = require('eslint').RuleTester

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

const template = (beforeRouteEnter) => `
<template>
<p>{{ greeting }} World!</p>
</template>

<script>
export default {
data () {
return {
greeting: "Hello"
};
},
beforeRouteEnter() {
${beforeRouteEnter}
}
};
</script>

<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>`

const functionTemplate = (beforeRouteEnter) => `
<template>
<p>{{ greeting }} World!</p>
</template>

<script>
export default {
data () {
return {
greeting: "Hello"
};
},
beforeRouteEnter: function() {
${beforeRouteEnter}
}
};
</script>`

const ruleTester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
})
ruleTester.run('no-this-in-before-route-enter', rule, {
valid: [
template(''),
template('const variable = 42;'),
template('someFunction(42)'),
`
<template>
<p>{{ greeting }} World!</p>
</template>

<script>
export default {
data () {
return {
greeting: "Hello"
};
},
};`
],
invalid: [
{
code: template(`this.xxx();`),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
Copy link
Member

Choose a reason for hiding this comment

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

Would you test the location of the error, including line and column? Also, test the message as text.

}
]
},
{
code: functionTemplate('this.method();'),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
}
]
},
{
code: template('this.attr = this.method();'),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
}
]
},
{
code: functionTemplate('this.attr = this.method();'),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
}
]
},
{
code: template(`
if (this.method()) {}
`),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
}
]
},
{
code: functionTemplate(`
if (true) { this.method(); }
`),
filename: 'ValidComponent.vue',
errors: [
{
message: rule.errorMessage
}
]
}
]
})