From 2870d51d551ef41dae5356650af05049d2daa5df Mon Sep 17 00:00:00 2001 From: Przemyslaw Jan Beigert Date: Wed, 2 Jun 2021 19:51:26 +0200 Subject: [PATCH 1/6] feat(no-this-in-before-router-enter): create rule --- docs/rules/no-this-in-before-route-enter.md | 72 +++++++++ lib/index.js | 1 + lib/rules/no-this-in-before-route-enter.js | 95 ++++++++++++ .../rules/no-this-in-before-route-enter.js | 144 ++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 docs/rules/no-this-in-before-route-enter.md create mode 100644 lib/rules/no-this-in-before-route-enter.js create mode 100644 tests/lib/rules/no-this-in-before-route-enter.js diff --git a/docs/rules/no-this-in-before-route-enter.md b/docs/rules/no-this-in-before-route-enter.md new file mode 100644 index 000000000..6bfd82321 --- /dev/null +++ b/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 + +``` + +Bad: +```js + +``` + +Bad: +```js + +``` + +Good: +```js + +``` + +### 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) diff --git a/lib/index.js b/lib/index.js index 1379e284e..2966d8759 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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'), diff --git a/lib/rules/no-this-in-before-route-enter.js b/lib/rules/no-this-in-before-route-enter.js new file mode 100644 index 000000000..ae6752835 --- /dev/null +++ b/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 + }) + } + }) + } +} diff --git a/tests/lib/rules/no-this-in-before-route-enter.js b/tests/lib/rules/no-this-in-before-route-enter.js new file mode 100644 index 000000000..9040d1e52 --- /dev/null +++ b/tests/lib/rules/no-this-in-before-route-enter.js @@ -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) => ` + + + + +` + +const functionTemplate = (beforeRouteEnter) => ` + + +` + +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)'), + ` + + + +``` + +Bad: +```js + +``` + +Bad: +```js + +``` + +Good: +```js + +``` + +### Options + +Nothing. + +## 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) diff --git a/lib/index.js b/lib/index.js index 1379e284e..2966d8759 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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'), diff --git a/lib/rules/no-this-in-before-route-enter.js b/lib/rules/no-this-in-before-route-enter.js new file mode 100644 index 000000000..a72c111c6 --- /dev/null +++ b/lib/rules/no-this-in-before-route-enter.js @@ -0,0 +1,90 @@ +/** + * @fileoverview Don't use this in a beforeRouteEnter method + * @author Przemyslaw Jan Beigert + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow this usage in a beforeRouteEnter method', + categories: null, + }, + fixable: null, + schema: [], + messages: { + disallow: + "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards." + } + }, + /** @param {RuleContext} context */ + create(context) { + /** + * @typedef {object} ScopeStack + * @property {ScopeStack | null} upper + * @property {FunctionExpression | FunctionDeclaration} node + * @property {boolean} beforeRouteEnter + */ + /** @type {Set} */ + const beforeRouteEnterFunctions = new Set() + /** @type {ScopeStack | null} */ + let scopeStack = null + + /** + * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node + */ + function onFunctionEnter(node) { + if (node.type === 'ArrowFunctionExpression') { + return + } + scopeStack = { + upper: scopeStack, + node, + beforeRouteEnter: beforeRouteEnterFunctions.has( + /** @type {never} */ (node) + ) + } + } + + /** + * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node + */ + function onFunctionExit(node) { + if (scopeStack && scopeStack.node === node) { + scopeStack = scopeStack.upper + } + } + return utils.defineVueVisitor(context, { + onVueObjectEnter(node) { + const beforeRouteEnter = utils.findProperty(node, 'beforeRouteEnter') + if ( + beforeRouteEnter && + beforeRouteEnter.value.type === 'FunctionExpression' + ) { + beforeRouteEnterFunctions.add(beforeRouteEnter.value) + } + }, + ':function': onFunctionEnter, + ':function:exit': onFunctionExit, + ThisExpression(node) { + if (scopeStack && scopeStack.beforeRouteEnter) { + context.report({ + node, + messageId: 'disallow' + }) + } + } + }) + } +} diff --git a/tests/lib/rules/no-this-in-before-route-enter.js b/tests/lib/rules/no-this-in-before-route-enter.js new file mode 100644 index 000000000..9040d1e52 --- /dev/null +++ b/tests/lib/rules/no-this-in-before-route-enter.js @@ -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) => ` + + + + +` + +const functionTemplate = (beforeRouteEnter) => ` + + +` + +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)'), + ` + + + ``` + + Bad: -```js + + + +```vue ``` + + Bad: -```js + + + +```vue ``` + + + +Bad: + + + +```vue + +``` + + + Good: -```js + + + +```vue ``` + + ### Options Nothing. ## When Not To Use It -Give a short description of when it would be appropriate to turn off this rule. +When [vue-router](https://router.vuejs.org/) is not installed. ## 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) diff --git a/lib/index.js b/lib/index.js index 2966d8759..20ecde959 100644 --- a/lib/index.js +++ b/lib/index.js @@ -113,7 +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-this-in-before-route-enter': 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'), diff --git a/lib/rules/no-this-in-before-route-enter.js b/lib/rules/no-this-in-before-route-enter.js index a72c111c6..968f45c18 100644 --- a/lib/rules/no-this-in-before-route-enter.js +++ b/lib/rules/no-this-in-before-route-enter.js @@ -20,6 +20,7 @@ module.exports = { docs: { description: 'disallow this usage in a beforeRouteEnter method', categories: null, + url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html' }, fixable: null, schema: [],