Skip to content

Commit c283697

Browse files
feat(no-this-in-before-router-enter): create rule
1 parent c775584 commit c283697

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# vue/no-this-in-before-route-enter
2+
3+
> 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
4+
5+
## Rule Details
6+
7+
Bad:
8+
```js
9+
<script>
10+
export default {
11+
beforeRouteEnter() {
12+
this.method(); // Uncaught TypeError: Cannot read property 'method' of undefined
13+
}
14+
}
15+
</script>
16+
```
17+
18+
Bad:
19+
```js
20+
<script>
21+
export default {
22+
beforeRouteEnter() {
23+
this.attribute = 42;
24+
}
25+
}
26+
</script>
27+
```
28+
29+
Bad:
30+
```js
31+
<script>
32+
export default {
33+
beforeRouteEnter() {
34+
if (this.value === 42) {
35+
36+
}
37+
}
38+
}
39+
</script>
40+
```
41+
42+
Good:
43+
```js
44+
<script>
45+
export default {
46+
beforeRouteEnter() {
47+
// anything without this
48+
}
49+
}
50+
</script>
51+
```
52+
53+
### Options
54+
55+
If there are any options, describe them here. Otherwise, delete this section.
56+
57+
## When Not To Use It
58+
59+
Give a short description of when it would be appropriate to turn off this rule.
60+
61+
## Further Reading
62+
63+
[vue-router - in-component-guards](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards)
64+
65+
## :rocket: Version
66+
67+
This rule was introduced in eslint-plugin-vue 7.11.0
68+
69+
## :mag: Implementation
70+
71+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-this-in-before-route-enter.js)
72+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-this-in-before-route-enter.js)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ module.exports = {
113113
'no-template-shadow': require('./rules/no-template-shadow'),
114114
'no-template-target-blank': require('./rules/no-template-target-blank'),
115115
'no-textarea-mustache': require('./rules/no-textarea-mustache'),
116+
'no-this-in-before-route': require('./rules/no-this-in-before-route-enter'),
116117
'no-unregistered-components': require('./rules/no-unregistered-components'),
117118
'no-unsupported-features': require('./rules/no-unsupported-features'),
118119
'no-unused-components': require('./rules/no-unused-components'),
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* @fileoverview Don't use this in a beforeRouteEnter method
3+
* @author Przemyslaw Jan Beigert
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
const utils = require('../utils');
12+
13+
"use strict";
14+
/**
15+
* @param {FunctionExpression | BlockStatement | Property | ThisExpression} obj
16+
* @returns {boolean}
17+
*/
18+
function deepFindThisExpression(obj) {
19+
if (typeof obj !== 'object' || !obj) {
20+
return false;
21+
}
22+
if (obj.type === 'ThisExpression') {
23+
return true;
24+
}
25+
26+
/** @param {typeof obj} key */
27+
return Object.entries(obj).some(([key, value]) => {
28+
if (key === 'parent') {
29+
return false;
30+
}
31+
if (Array.isArray(value)) {
32+
return value.some(item => deepFindThisExpression(item));
33+
}
34+
if (typeof value === 'object') {
35+
return deepFindThisExpression(value);
36+
}
37+
38+
return false;
39+
})
40+
}
41+
42+
/**
43+
* @param {Property | SpreadElement} property
44+
* @returns {property is Property}
45+
*/
46+
function isPropertyBeforeRouteMethod(property) {
47+
if (property.type !== 'Property') {
48+
return false;
49+
}
50+
51+
return property.key.type === 'Identifier' && property.key.name === 'beforeRouteEnter';
52+
}
53+
54+
const errorMessage = 'beforeRouteEnter does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards'
55+
56+
module.exports = {
57+
errorMessage: errorMessage,
58+
meta: {
59+
type: 'suggestion',
60+
docs: {
61+
description: 'Do not this in beforeRouteEnter',
62+
categories: null,
63+
url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html'
64+
},
65+
},
66+
67+
/** @param {RuleContext} context */
68+
create: function (context) {
69+
// ----------------------------------------------------------------------
70+
// Public
71+
// ----------------------------------------------------------------------
72+
return utils.executeOnVue(context, (obj) => {
73+
const beforeRouteProperty = obj.properties.find(isPropertyBeforeRouteMethod);
74+
if (!beforeRouteProperty) {
75+
return;
76+
}
77+
if (beforeRouteProperty.value.type !== 'FunctionExpression') {
78+
return;
79+
}
80+
if (deepFindThisExpression(beforeRouteProperty.value.body)) {
81+
context.report({
82+
node: beforeRouteProperty,
83+
message: errorMessage,
84+
});
85+
}
86+
});
87+
}
88+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* @fileoverview Don&#39;t use &#34;this&#34; i a beforeRouteEnter method
3+
* @author Przemyslaw Jan Beigert
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
var rule = require("../../../lib/rules/no-this-in-before-route-enter");
12+
var RuleTester = require("eslint").RuleTester;
13+
14+
//------------------------------------------------------------------------------
15+
// Tests
16+
//------------------------------------------------------------------------------
17+
18+
const template = beforeRouteEnter => `
19+
<template>
20+
<p>{{ greeting }} World!</p>
21+
</template>
22+
23+
<script>
24+
export default {
25+
data () {
26+
return {
27+
greeting: "Hello"
28+
};
29+
},
30+
beforeRouteEnter() {
31+
${beforeRouteEnter}
32+
}
33+
};
34+
</script>
35+
36+
<style scoped>
37+
p {
38+
font-size: 2em;
39+
text-align: center;
40+
}
41+
</style>`;
42+
43+
const functionTemplate = beforeRouteEnter => `
44+
<template>
45+
<p>{{ greeting }} World!</p>
46+
</template>
47+
48+
<script>
49+
export default {
50+
data () {
51+
return {
52+
greeting: "Hello"
53+
};
54+
},
55+
beforeRouteEnter: function() {
56+
${beforeRouteEnter}
57+
}
58+
};
59+
</script>`;
60+
61+
const ruleTester = new RuleTester({
62+
parser: require.resolve('vue-eslint-parser'),
63+
parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
64+
})
65+
ruleTester.run("no-this-in-before-route-enter", rule, {
66+
valid: [
67+
template(''),
68+
template('const variable = 42;'),
69+
template('someFunction(42)'),
70+
`
71+
<template>
72+
<p>{{ greeting }} World!</p>
73+
</template>
74+
75+
<script>
76+
export default {
77+
data () {
78+
return {
79+
greeting: "Hello"
80+
};
81+
},
82+
};`
83+
],
84+
invalid: [
85+
{
86+
code: template(`this.xxx();`),
87+
filename: 'ValidComponent.vue',
88+
errors: [{
89+
message: rule.errorMessage,
90+
}]
91+
},
92+
{
93+
code: functionTemplate('this.method();'),
94+
filename: 'ValidComponent.vue',
95+
errors: [{
96+
message: rule.errorMessage,
97+
}]
98+
},
99+
{
100+
code: template('this.attr = this.method();'),
101+
filename: 'ValidComponent.vue',
102+
errors: [{
103+
message: rule.errorMessage,
104+
}]
105+
},
106+
{
107+
code: functionTemplate('this.attr = this.method();'),
108+
filename: 'ValidComponent.vue',
109+
errors: [{
110+
message: rule.errorMessage,
111+
}]
112+
},
113+
{
114+
code: template(`
115+
if (this.method()) {}
116+
`),
117+
filename: 'ValidComponent.vue',
118+
errors: [{
119+
message: rule.errorMessage,
120+
}],
121+
},
122+
{
123+
code: functionTemplate(`
124+
if (true) { this.method(); }
125+
`),
126+
filename: 'ValidComponent.vue',
127+
errors: [{
128+
message: rule.errorMessage,
129+
}]
130+
}
131+
]
132+
});

0 commit comments

Comments
 (0)