From 42ada82b306c3c259388392da565bd5fef260c76 Mon Sep 17 00:00:00 2001 From: Clark Du Date: Mon, 10 Dec 2018 15:06:24 +0000 Subject: [PATCH] feat: add no-env-in-context --- docs/rules/no-env-in-context.md | 52 ++++++++ lib/configs/base.js | 4 +- lib/rules/__test__/no-env-in-context.test.js | 125 +++++++++++++++++++ lib/rules/no-env-in-context.js | 83 ++++++++++++ lib/utils/index.js | 21 ++-- 5 files changed, 275 insertions(+), 10 deletions(-) create mode 100644 docs/rules/no-env-in-context.md create mode 100644 lib/rules/__test__/no-env-in-context.test.js create mode 100644 lib/rules/no-env-in-context.js diff --git a/docs/rules/no-env-in-context.md b/docs/rules/no-env-in-context.md new file mode 100644 index 0000000..92d38cf --- /dev/null +++ b/docs/rules/no-env-in-context.md @@ -0,0 +1,52 @@ +# nuxt/no-env-in-context + +> disallow `context.isServer/context.isClient` in `asyncData/fetch/nuxtServerInit` + +- :gear: This rule is included in `"plugin:nuxt/base"`. + +## Rule Details + +This rule is for preventing using `context.isServer/context.isClient` in `asyncData/fetch/nuxtServerInit` + +Examples of **incorrect** code for this rule: + +```js + +export default { + async asyncData() { + if(process.server) { + const foo = 'bar' + } + }, + fetch() { + if(process.client) { + const foo = 'bar' + } + } +} + +``` + +Examples of **correct** code for this rule: + +```js + +export default { + asyncData(context) { + if(context.isServer) { + const foo = 'bar' + } + }, + fetch({ isClient }) { + if(isClient) { + const foo = 'bar' + } + } +} + +``` + +## :mag: Implementation + +- [Rule source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/no-env-in-context.js) +- [Test source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/__test__/no-env-in-context.test.js) \ No newline at end of file diff --git a/lib/configs/base.js b/lib/configs/base.js index 755ceb4..e4007d6 100644 --- a/lib/configs/base.js +++ b/lib/configs/base.js @@ -12,7 +12,7 @@ module.exports = { es6: true }, rules: { - 'nuxt/no-this-in-fetch-data': 'error', - 'nuxt/no-this-in-fetch': 'error' + 'nuxt/no-env-in-context': 'error', + 'nuxt/no-this-in-fetch-data': 'error' } } diff --git a/lib/rules/__test__/no-env-in-context.test.js b/lib/rules/__test__/no-env-in-context.test.js new file mode 100644 index 0000000..ee5b66b --- /dev/null +++ b/lib/rules/__test__/no-env-in-context.test.js @@ -0,0 +1,125 @@ +/** + * @fileoverview disallow `context.isServer/context.isClient` in `asyncData/fetch/nuxtServerInit` + * @author Xin Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../no-env-in-context') + +var RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester() +ruleTester.run('no-env-in-context', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + async asyncData() { + if(process.server) { + const foo = 'bar' + } + }, + fetch() { + if(process.client) { + const foo = 'bar' + } + } + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + asyncData(context) { + if(context.isServer) { + const foo = 'bar' + } + }, + fetch(context) { + if(context.isClient) { + const foo = 'bar' + } + } + } + `, + errors: [{ + message: 'Unexpected isServer in asyncData.', + type: 'MemberExpression' + }, { + message: 'Unexpected isClient in fetch.', + type: 'MemberExpression' + }], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + asyncData(context) { + if(context['isClient']) { + const foo = 'bar' + } + }, + fetch(context) { + if(context['isServer']) { + const foo = 'bar' + } + } + } + `, + errors: [{ + message: 'Unexpected isClient in asyncData.', + type: 'MemberExpression' + }, { + message: 'Unexpected isServer in fetch.', + type: 'MemberExpression' + }], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + asyncData({ isClient }) { + if(isClient) { + const foo = 'bar' + } + }, + fetch({ isServer }) { + if(isServer) { + const foo = 'bar' + } + } + } + `, + errors: [{ + message: 'Unexpected isClient in asyncData.', + type: 'Property' + }, { + message: 'Unexpected isServer in fetch.', + type: 'Property' + }], + parserOptions + } + ] +}) diff --git a/lib/rules/no-env-in-context.js b/lib/rules/no-env-in-context.js new file mode 100644 index 0000000..6509ed7 --- /dev/null +++ b/lib/rules/no-env-in-context.js @@ -0,0 +1,83 @@ +/** + * @fileoverview disallow `context.isServer/context.isClient` in `asyncData/fetch/nuxtServerInit` + * @author Xin Du + */ +'use strict' + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: + 'disallow `context.isServer/context.isClient` in `asyncData/fetch/nuxtServerInit`', + category: 'base' + }, + messages: { + noEnv: 'Unexpected {{env}} in {{funcName}}.' + } + }, + + create: function (context) { + // variables should be defined here + const forbiddenNodes = [] + const options = context.options[0] || {} + + const ENV = ['isServer', 'isClient'] + const HOOKS = new Set(['asyncData', 'fetch'].concat(options.methods || [])) + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + MemberExpression (node) { + const propertyName = node.computed ? node.property.value : node.property.name + if (propertyName && ENV.includes(propertyName)) { + forbiddenNodes.push({ name: propertyName, node }) + } + }, + ...utils.executeOnVue(context, obj => { + for (const funcName of HOOKS) { + const func = utils.getFunctionWithName(obj, funcName) + const param = func.value.params && func.value.params[0] + if (param) { + if (param.type === 'ObjectPattern') { + for (const prop of param.properties) { + if (ENV.includes(prop.key.name)) { + context.report({ + node: prop, + messageId: 'noEnv', + data: { + env: prop.key.name, + funcName + } + }) + } + } + } else { + for (const { name, node: child } of forbiddenNodes) { + if (utils.isInFunction(func, child)) { + if (param.name === child.object.name) { + context.report({ + node: child, + messageId: 'noEnv', + data: { + env: name, + funcName + } + }) + } + } + } + } + } + } + }) + } + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index 8f5dcaf..e37c95d 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -19,6 +19,17 @@ module.exports = Object.assign( item => item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression' ) }, + isInFunction (func, child) { + if (func.value.type === 'FunctionExpression') { + if ( + child && + child.loc.start.line >= func.value.loc.start.line && + child.loc.end.line <= func.value.loc.end.line + ) { + return true + } + } + }, * getFunctionWithChild (rootNode, funcNames, childNodes) { const funcNodes = this.getProperties(rootNode, funcNames) @@ -27,14 +38,8 @@ module.exports = Object.assign( const funcName = utils.getStaticPropertyName(func.key) if (!funcName) continue - if (func.value.type === 'FunctionExpression') { - if ( - child && - child.loc.start.line >= func.value.loc.start.line && - child.loc.end.line <= func.value.loc.end.line - ) { - yield { name, node: child, funcName } - } + if (this.isInFunction(func, child)) { + yield { name, node: child, func, funcName } } } }