From 09eaa505a2a4691b94f7e1ae7e7ce2e19d5042c3 Mon Sep 17 00:00:00 2001 From: Clark Du Date: Thu, 6 Dec 2018 16:29:57 +0000 Subject: [PATCH] feat(rule): add no-globals-in-created --- docs/rules/no-globals-in-created.md | 38 ++++++ lib/configs/ssr.js | 6 + .../__test__/no-globals-in-created.test.js | 109 ++++++++++++++++++ .../__test__/no-this-in-async-data.test.js | 2 +- lib/rules/__test__/no-this-in-fetch.test.js | 2 +- lib/rules/no-globals-in-created.js | 74 ++++++++++++ lib/utils/index.js | 32 ++++- 7 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 docs/rules/no-globals-in-created.md create mode 100644 lib/configs/ssr.js create mode 100644 lib/rules/__test__/no-globals-in-created.test.js create mode 100644 lib/rules/no-globals-in-created.js diff --git a/docs/rules/no-globals-in-created.md b/docs/rules/no-globals-in-created.md new file mode 100644 index 0000000..da5acfc --- /dev/null +++ b/docs/rules/no-globals-in-created.md @@ -0,0 +1,38 @@ +# nuxt/no-globals-in-created + +> disallow `window/document` in `created/beforeCreate` + +- :gear: This rule is included in `"plugin:nuxt/ssr"`. + +## Rule Details + +This rule is for preventing using `window/document` in `created/beforeCreate`, since `created/beforeCreate` may be executed in server side in SSR. + +Examples of **incorrect** code for this rule: + +```js + +export default { + created() { + window.foo = 'bar' + } +} + +``` + +Examples of **correct** code for this rule: + +```js + +export default { + created() { + const foo = 'bar' + } +} + +``` + +## :mag: Implementation + +- [Rule source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/no-globals-in-created.js) +- [Test source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/__test__/no-globals-in-created.test.js) diff --git a/lib/configs/ssr.js b/lib/configs/ssr.js new file mode 100644 index 0000000..665639a --- /dev/null +++ b/lib/configs/ssr.js @@ -0,0 +1,6 @@ +module.exports = { + extends: require.resolve('./base.js'), + rules: { + 'nuxt/no-globals-in-created': 'warn' + } +} diff --git a/lib/rules/__test__/no-globals-in-created.test.js b/lib/rules/__test__/no-globals-in-created.test.js new file mode 100644 index 0000000..2160e8a --- /dev/null +++ b/lib/rules/__test__/no-globals-in-created.test.js @@ -0,0 +1,109 @@ +/** + * @fileoverview disallow `window/document` in `created/beforeCreate` + * @author Clark Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../no-globals-in-created') + +var RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester() +ruleTester.run('no-globals-in-created', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + created() { + const path = this.$route.path + }, + beforeCreate() { + const path = this.$route.params.foo + } + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + created() { + const path = window.location.pathname + }, + beforeCreate() { + const foo = document.foo + } + } + `, + errors: [{ + message: 'Unexpected window in created.', + type: 'MemberExpression' + }, { + message: 'Unexpected document in beforeCreate.', + type: 'MemberExpression' + }], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + created() { + document.foo = 'bar' + }, + beforeCreate() { + window.foo = 'bar' + } + } + `, + errors: [{ + message: 'Unexpected document in created.', + type: 'MemberExpression' + }, { + message: 'Unexpected window in beforeCreate.', + type: 'MemberExpression' + }], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + created() { + return window.foo + }, + beforeCreate() { + return document.foo + } + } + `, + errors: [{ + message: 'Unexpected window in created.', + type: 'MemberExpression' + }, { + message: 'Unexpected document in beforeCreate.', + type: 'MemberExpression' + }], + parserOptions + } + ] +}) diff --git a/lib/rules/__test__/no-this-in-async-data.test.js b/lib/rules/__test__/no-this-in-async-data.test.js index a42ed66..c6fae48 100644 --- a/lib/rules/__test__/no-this-in-async-data.test.js +++ b/lib/rules/__test__/no-this-in-async-data.test.js @@ -1,5 +1,5 @@ /** - * @fileoverview Prevent using this in asyncData + * @fileoverview disallow `this` in asyncData * @author Clark Du */ 'use strict' diff --git a/lib/rules/__test__/no-this-in-fetch.test.js b/lib/rules/__test__/no-this-in-fetch.test.js index a31eef3..b64c1ad 100644 --- a/lib/rules/__test__/no-this-in-fetch.test.js +++ b/lib/rules/__test__/no-this-in-fetch.test.js @@ -1,5 +1,5 @@ /** - * @fileoverview Prevent using this in fetch + * @fileoverview disallow `this` in fetch * @author Clark Du */ 'use strict' diff --git a/lib/rules/no-globals-in-created.js b/lib/rules/no-globals-in-created.js new file mode 100644 index 0000000..c18f252 --- /dev/null +++ b/lib/rules/no-globals-in-created.js @@ -0,0 +1,74 @@ +/** + * @fileoverview disallow `window/document` in `created/beforeCreate` + * @author Clark Du + */ +'use strict' + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'disallow `window/document` in `created/beforeCreate`', + category: 'ssr' + }, + fixable: null, // or "code" or "whitespace" + schema: [ + // fill in your schema + ], + messages: { + noGlobals: 'Unexpected {{name}} in {{funcName}}.' + } + }, + + create: function (context) { + const forbiddenNodes = [] + const options = context.options[0] || {} + + const HOOKS = new Set( + ['created', 'beforeCreate'].concat(options.methods || []) + ) + const GLOBALS = ['window', 'document'] + + function isGlobals (name) { + return GLOBALS.includes(name) + } + + return { + MemberExpression (node) { + if (!node.object) return + + const name = node.object.name + + if (isGlobals(name)) { + forbiddenNodes.push({ name, node }) + } + }, + VariableDeclarator (node) { + if (!node.init) return + + const name = node.init.name + + if (isGlobals(name)) { + forbiddenNodes.push({ name, node }) + } + }, + ...utils.executeOnVue(context, obj => { + for (const { funcName, name, node } of utils.getParentFuncs(obj, HOOKS, forbiddenNodes)) { + context.report({ + node, + messageId: 'noGlobals', + data: { + name, + funcName + } + }) + } + }) + } + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index e5a37a3..36baf58 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -2,11 +2,35 @@ const utils = require('eslint-plugin-vue/lib/utils') module.exports = Object.assign( { - getFuncNodeWithName (node, name) { - return node.properties.find(item => item.type === 'Property' && - utils.getStaticPropertyName(item) === name && - (item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression') + getProperties (node, names) { + return node.properties.filter( + p => p.type === 'Property' && (!names.size || names.has(utils.getStaticPropertyName(p.key))) ) + }, + getFuncNodeWithName (rootNode, name) { + return this.getProperties(rootNode, [name]).filter( + item => item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression' + ) + }, + * getParentFuncs (rootNode, parentNames, childNodes) { + const nodes = this.getProperties(rootNode, parentNames) + + for (const item of nodes) { + for (const { name, node: child } of childNodes) { + const funcName = utils.getStaticPropertyName(item.key) + if (!funcName) continue + + if (item.value.type === 'FunctionExpression') { + if ( + child && + child.loc.start.line >= item.value.loc.start.line && + child.loc.end.line <= item.value.loc.end.line + ) { + yield { name, node: child, funcName } + } + } + } + } } }, utils