Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(no-this-in-before-router-enter): create rule
- Loading branch information
1 parent
e1815cd
commit 2870d51
Showing
4 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: | ||
```js | ||
<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. | ||
|
||
## 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
] | ||
}, | ||
{ | ||
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 | ||
} | ||
] | ||
} | ||
] | ||
}) |