Skip to content

Commit

Permalink
feat(no-this-in-before-router-enter): create rule
Browse files Browse the repository at this point in the history
  • Loading branch information
przemyslawjanpietrzak committed Jun 2, 2021
1 parent c775584 commit c283697
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 0 deletions.
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

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)
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
88 changes: 88 additions & 0 deletions lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,88 @@
/**
* @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: errorMessage,
meta: {
type: 'suggestion',
docs: {
description: 'Do not this in beforeRouteEnter',
categories: null,
url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html'
},
},

/** @param {RuleContext} context */
create: function (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,
});
}
});
}
};
132 changes: 132 additions & 0 deletions tests/lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,132 @@
/**
* @fileoverview Don&#39;t use &#34;this&#34; i a beforeRouteEnter method
* @author Przemyslaw Jan Beigert
*/
"use strict";

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

var rule = require("../../../lib/rules/no-this-in-before-route-enter");
var 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,
}]
}
]
});

0 comments on commit c283697

Please sign in to comment.