diff --git a/README.md b/README.md index 3c2b8c6..4446020 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ This plugin provides four predefined configs: | | [nuxt/no-env-in-hooks](./docs/rules/no-env-in-hooks.md) | Disallow `process.server/process.client` in client only Vue lifecycle hooks like: `mounted, beforeMount, updated...` | | | [nuxt/no-globals-in-created](./docs/rules/no-globals-in-created.md) | Disallow `window/document` in `created/beforeCreate` | | | [nuxt/no-this-in-fetch-data](./docs/rules/no-this-in-fetch-data.md) | Disallow `this` in `asyncData/fetch` | +| | [nuxt/no-cjs-in-config](./docs/rules/no-cjs-in-config.md) | Disallow `require/modules.exports/exports` in `nuxt.config.js` | ### Recommended Rules diff --git a/docs/rules/no-cjs-in-config.md b/docs/rules/no-cjs-in-config.md new file mode 100644 index 0000000..393a1c1 --- /dev/null +++ b/docs/rules/no-cjs-in-config.md @@ -0,0 +1,40 @@ +# nuxt/no-cjs-in-config + +> Disallow commonjs module api `require/modules.exports/exports` in `nuxt.config.js` + +- :gear: This rule is included in `"plugin:nuxt/base"`. + +## Rule Details + +This rule is for preventing using `require/modules.exports/exportst` in `nuxt.config.js` + +Examples of **incorrect** code for this rule: + +```js + +const { name } = require('./package.json') + +module.exports = { + mode: 'universal', + name +} + +``` + +Examples of **correct** code for this rule: + +```js + +import { name } from './package.json' + +export default { + mode: 'universal', + name +} + +``` + +## :mag: Implementation + +- [Rule source](../../lib/rules/no-cjs-in-config.js) +- [Test source](../../lib/rules/__test__/no-cjs-in-config.test.js) diff --git a/lib/configs/base.js b/lib/configs/base.js index 536b164..3eda96d 100644 --- a/lib/configs/base.js +++ b/lib/configs/base.js @@ -18,6 +18,7 @@ module.exports = { 'nuxt/no-env-in-context': 'error', 'nuxt/no-env-in-hooks': 'error', 'nuxt/no-globals-in-created': 'error', - 'nuxt/no-this-in-fetch-data': 'error' + 'nuxt/no-this-in-fetch-data': 'error', + 'nuxt/no-cjs-in-config': 'error' } } diff --git a/lib/index.js b/lib/index.js index dcbcefa..0b7715c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,8 @@ module.exports = { 'no-env-in-hooks': require('./rules/no-env-in-hooks'), 'no-globals-in-created': require('./rules/no-globals-in-created'), 'no-this-in-fetch-data': require('./rules/no-this-in-fetch-data'), - 'no-timing-in-fetch-data': require('./rules/no-timing-in-fetch-data') + 'no-timing-in-fetch-data': require('./rules/no-timing-in-fetch-data'), + 'no-cjs-in-config': require('./rules/no-cjs-in-config') }, configs: { 'base': require('./configs/base'), diff --git a/lib/rules/__test__/no-cjs-in-config.test.js b/lib/rules/__test__/no-cjs-in-config.test.js new file mode 100644 index 0000000..0d39e6c --- /dev/null +++ b/lib/rules/__test__/no-cjs-in-config.test.js @@ -0,0 +1,83 @@ +/** + * @fileoverview Disallow `require/modules.exports/exports` in `nuxt.config.js` + * @author Xin Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../no-cjs-in-config') + +var RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester() +ruleTester.run('no-cjs-in-config', rule, { + + valid: [ + { + filename: 'nuxt.config.js', + code: ` + import { name } from './package.json' + + export default { + mode: 'universal', + name + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'nuxt.config.js', + code: ` + const { name } = require('./package.json') + `, + errors: [{ + message: 'Unexpected require, please use import instead.', + type: 'Identifier' + }], + parserOptions + }, + { + filename: 'nuxt.config.js', + code: ` + module.exports = { + mode: 'universal', + name + } + `, + errors: [{ + message: 'Unexpected module.exports, please use export default instead.', + type: 'MemberExpression' + }], + parserOptions + }, + { + filename: 'nuxt.config.js', + code: ` + exports.test = { + mode: 'universal', + name + } + `, + errors: [{ + message: 'Unexpected exports, please use export default instead.', + type: 'MemberExpression' + }], + parserOptions + } + ] +}) diff --git a/lib/rules/no-cjs-in-config.js b/lib/rules/no-cjs-in-config.js new file mode 100644 index 0000000..bc2757e --- /dev/null +++ b/lib/rules/no-cjs-in-config.js @@ -0,0 +1,101 @@ +/** + * @fileoverview Disallow `require/modules.exports/exports` in `nuxt.config.js` + * @author Xin Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: + 'disallow commonjs module api `require/modules.exports/exports` in `nuxt.config.js`', + category: 'base' + }, + messages: { + noCjs: 'Unexpected {{cjs}}, please use {{esm}} instead.' + } + }, + + create (context) { + // variables should be defined here + const options = context.options[0] || {} + const configFile = options.file || 'nuxt.config.js' + let isNuxtConfig = false + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + Program (node) { + const filename = context.getFilename() + if (filename === configFile) { + isNuxtConfig = true + } + }, + MemberExpression: function (node) { + if (!isNuxtConfig) { + return + } + + // module.exports + if (node.object.name === 'module' && node.property.name === 'exports') { + context.report({ + node, + messageId: 'noCjs', + data: { + cjs: 'module.exports', + esm: 'export default' + } + }) + } + + // exports. + if (node.object.name === 'exports') { + const isInScope = context.getScope() + .variables + .some(variable => variable.name === 'exports') + if (!isInScope) { + context.report({ + node, + messageId: 'noCjs', + data: { + cjs: 'exports', + esm: 'export default' + } + }) + } + } + }, + CallExpression: function (call) { + const module = call.arguments[0] + + if ( + !isNuxtConfig || + context.getScope().type !== 'module' || + !['ExpressionStatement', 'VariableDeclarator'].includes(call.parent.type) || + call.callee.type !== 'Identifier' || + call.callee.name !== 'require' || + call.arguments.length !== 1 || + module.type !== 'Literal' || + typeof module.value !== 'string' + ) { + return + } + + context.report({ + node: call.callee, + messageId: 'noCjs', + data: { + cjs: 'require', + esm: 'import' + } + }) + } + } + } +}