diff --git a/docs/rules/no-timing-in-fetch-data.md b/docs/rules/no-timing-in-fetch-data.md new file mode 100644 index 0000000..7647a88 --- /dev/null +++ b/docs/rules/no-timing-in-fetch-data.md @@ -0,0 +1,50 @@ +# nuxt/no-timing-in-fetch-data + +> disallow `setTimeout/setInterval` in `asyncData/fetch` + +- :gear: This rule is included in `"plugin:nuxt/recommended"`. + +## Rule Details + +This rule is for preventing using `setTimeout/setInterval` in `asyncData/fetch` since it may lead to memory leak + +Examples of **incorrect** code for this rule: + +```js + +export default { + async asyncData() { + let foo = 'baz' + }, + fetch() { + let foo = 'baz' + } +} + +``` + +Examples of **correct** code for this rule: + +```js + +export default { + asyncData() { + let foo = 'bar' + setTimeout(() => { + foo = 'baz' + }, 0) + }, + fetch() { + let foo = 'bar' + setInterval(() => { + foo = 'baz' + }, 0) + } +} + +``` + +## :mag: Implementation + +- [Rule source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/no-timing-in-fetch-data.js) +- [Test source](https://github.com/nuxt/eslint-plugin-nuxt/blob/master/lib/rules/__test__/no-timing-in-fetch-data.test.js) \ No newline at end of file diff --git a/lib/configs/ssr.js b/lib/configs/ssr.js index 665639a..29bac9e 100644 --- a/lib/configs/ssr.js +++ b/lib/configs/ssr.js @@ -1,6 +1,7 @@ module.exports = { extends: require.resolve('./base.js'), rules: { - 'nuxt/no-globals-in-created': 'warn' + 'nuxt/no-globals-in-created': 'warn', + 'nuxt/no-timing-in-fetch-data': 'warn' } } diff --git a/lib/index.js b/lib/index.js index 26ca66a..bea4491 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,8 @@ module.exports = { 'no-this-in-fetch': require('./rules/no-this-in-fetch') }, configs: { - 'base': require('./configs/base') + 'base': require('./configs/base'), + 'ssr': require('./configs/ssr') }, processors: { '.vue': require('./processors') diff --git a/lib/rules/__test__/no-timing-in-fetch-data.test.js b/lib/rules/__test__/no-timing-in-fetch-data.test.js new file mode 100644 index 0000000..d53f9a5 --- /dev/null +++ b/lib/rules/__test__/no-timing-in-fetch-data.test.js @@ -0,0 +1,94 @@ +/** + * @fileoverview disallow `setTimeout/setInterval` in `asyncData/fetch` + * @author Xin Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../no-timing-in-fetch-data') + +var RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester() +ruleTester.run('no-timing-in-fetch-data', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + async asyncData() { + let foo = 'baz' + }, + fetch() { + let foo = 'baz' + } + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + asyncData() { + let foo = 'bar' + setTimeout(() => { + foo = 'baz' + }, 0) + }, + fetch() { + let foo = 'bar' + setInterval(() => { + foo = 'baz' + }, 0) + } + } + `, + errors: [{ + message: 'Unexpected setTimeout in asyncData.', + type: 'CallExpression' + }, { + message: 'Unexpected setInterval in fetch.', + type: 'CallExpression' + }], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + asyncData() { + let timer = setInterval + }, + fetch() { + let timer = setTimeout + } + } + `, + errors: [{ + message: 'Unexpected setInterval in asyncData.', + type: 'VariableDeclarator' + }, { + message: 'Unexpected setTimeout in fetch.', + type: 'VariableDeclarator' + }], + parserOptions + } + ] +}) diff --git a/lib/rules/no-timing-in-fetch-data.js b/lib/rules/no-timing-in-fetch-data.js new file mode 100644 index 0000000..b1ba3bb --- /dev/null +++ b/lib/rules/no-timing-in-fetch-data.js @@ -0,0 +1,70 @@ +/** + * @fileoverview disallow `setTimeout/setInterval` in `asyncData/fetch` + * @author Xin Du + */ +'use strict' + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'disallow `setTimeout/setInterval` in `asyncData/fetch`', + category: 'ssr' + }, + messages: { + noTiming: 'Unexpected {{name}} in {{funcName}}.' + } + }, + + create: function (context) { + const forbiddenNodes = [] + const options = context.options[0] || {} + + const HOOKS = new Set( + ['fetch', 'asyncData'].concat(options.methods || []) + ) + const TIMING = ['setTimeout', 'setInterval'] + + function isTiming (name) { + return TIMING.includes(name) + } + + return { + CallExpression: function (node) { + if (!node.callee) return + + const name = node.callee.name + + if (isTiming(name)) { + forbiddenNodes.push({ name, node }) + } + }, + VariableDeclarator (node) { + if (!node.init) return + + const name = node.init.name + + if (isTiming(name)) { + forbiddenNodes.push({ name, node }) + } + }, + ...utils.executeOnVue(context, obj => { + for (const { funcName, name, node } of utils.getFunctionWithChild(obj, HOOKS, forbiddenNodes)) { + context.report({ + node, + messageId: 'noTiming', + data: { + name, + funcName + } + }) + } + }) + } + } +}