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
Changes from 1 commit
2870d51
7ef747a
792dc1d
df6b1ae
9c8e458
188c81a
0c81b90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -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 | ||||||||
|
||||||||
Bad: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||||||||
```js | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is no content, delete it.
Suggested change
|
||||||||
## 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/** | ||
* @fileoverview Don't use "this" 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you test the location of the error, including |
||
} | ||
] | ||
}, | ||
{ | ||
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 | ||
} | ||
] | ||
} | ||
] | ||
}) |
There was a problem hiding this comment.
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.