From c975bea196663f46c5138765ddcb8d8dd04a858f Mon Sep 17 00:00:00 2001 From: Clark Du Date: Sat, 9 Nov 2019 21:01:01 +0000 Subject: [PATCH 1/3] fest(rule): add require-func-head in recommended --- README.md | 3 +- docs/rules/require-func-head.md | 40 +++++++++++++ lib/configs/recommended.js | 3 +- lib/index.js | 3 +- lib/rules/__tests__/require-func-head.test.js | 60 +++++++++++++++++++ lib/rules/require-func-head.js | 54 +++++++++++++++++ lib/utils/index.js | 22 +++++++ 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 docs/rules/require-func-head.md create mode 100644 lib/rules/__tests__/require-func-head.test.js create mode 100644 lib/rules/require-func-head.js diff --git a/README.md b/README.md index d483510..5aa5006 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,5 @@ Include all the below rules, as well as all priority rules in above categories, | | Rule ID | Description | |:---|:--------|:------------| -| :wrench: | [nuxt/no-timing-in-fetch-data](./docs/rules/no-timing-in-fetch-data.md) | Disallow `setTimeout/setInterval` in `asyncData/fetch` | \ No newline at end of file +| :wrench: | [nuxt/no-timing-in-fetch-data](./docs/rules/no-timing-in-fetch-data.md) | Disallow `setTimeout/setInterval` in `asyncData/fetch` | +| :wrench: | [nuxt/require-func-head](./docs/rules/require-func-head.md) | Enforce `head` property in component to be a function. | diff --git a/docs/rules/require-func-head.md b/docs/rules/require-func-head.md new file mode 100644 index 0000000..005fbf0 --- /dev/null +++ b/docs/rules/require-func-head.md @@ -0,0 +1,40 @@ +# nuxt/require-func-head + +> enforce `head` property in component to be a function. + +- :gear: This rule is included in `"plugin:nuxt/recommended"`. + +## Rule Details + +This rule is enforcing `head` property in component to be a function. + +Examples of **incorrect** code for this rule: + +```js + +export default { + head: { + title: "My page" + } +} + +``` + +Examples of **correct** code for this rule: + +```js + +export default { + head() { + return { + title: "My page" + } + } +} + +``` + +## :mag: Implementation + +- [Rule source](../../lib/rules/require-func-head.js) +- [Test source](../../lib/rules/__tests__/require-func-head.test.js) diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js index 2de652d..4d1b137 100644 --- a/lib/configs/recommended.js +++ b/lib/configs/recommended.js @@ -1,6 +1,7 @@ module.exports = { extends: require.resolve('./base.js'), rules: { - 'nuxt/no-timing-in-fetch-data': 'error' + 'nuxt/no-timing-in-fetch-data': 'error', + 'nuxt/require-func-head': 'error' } } diff --git a/lib/index.js b/lib/index.js index e44ae1c..2c7f7e2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,7 +5,8 @@ module.exports = { '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-cjs-in-config': require('./rules/no-cjs-in-config') + 'no-cjs-in-config': require('./rules/no-cjs-in-config'), + 'require-func-head': require('./rules/require-func-head') }, configs: { base: require('./configs/base'), diff --git a/lib/rules/__tests__/require-func-head.test.js b/lib/rules/__tests__/require-func-head.test.js new file mode 100644 index 0000000..f0483a9 --- /dev/null +++ b/lib/rules/__tests__/require-func-head.test.js @@ -0,0 +1,60 @@ +/** + * @fileoverview disallow `setTimeout/setInterval` in `asyncData/fetch` + * @author Xin Du + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../require-func-head') + +var RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester() +ruleTester.run('require-func-head', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + head() { + return { + title: "My page" + } + } + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + head: { + title: "My page" + } + } + `, + errors: [{ + message: '`head` property in component must be a function.', + type: 'Property' + }], + parserOptions + } + ] +}) diff --git a/lib/rules/require-func-head.js b/lib/rules/require-func-head.js new file mode 100644 index 0000000..f8b5c2f --- /dev/null +++ b/lib/rules/require-func-head.js @@ -0,0 +1,54 @@ +/** + * @fileoverview enforce component's head property to be a function. + * @author Xin Du + */ +'use strict' + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "enforce component's head property to be a function", + category: 'recommended' + }, + fixable: 'code', + messages: { + head: '`head` property in component must be a function.' + } + }, + + create (context) { + const sourceCode = context.getSourceCode() + + return utils.executeOnVueComponent(context, (obj) => { + obj.properties + .filter(p => + p.type === 'Property' && + p.key.type === 'Identifier' && + p.key.name === 'head' && + p.value.type !== 'FunctionExpression' && + p.value.type !== 'ArrowFunctionExpression' && + p.value.type !== 'Identifier' + ) + .forEach(p => { + context.report({ + node: p, + messageId: 'head', + fix (fixer) { + const tokens = utils.getFirstAndLastTokens(p.value, sourceCode) + + return [ + fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), + fixer.insertTextAfter(tokens.last, ';\n}') + ] + } + }) + }) + }) + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index e37c95d..727ab0f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -43,6 +43,28 @@ module.exports = Object.assign( } } } + }, + isOpenParen (token) { + return token.type === 'Punctuator' && token.value === '(' + }, + isCloseParen (token) { + return token.type === 'Punctuator' && token.value === ')' + }, + getFirstAndLastTokens (node, sourceCode) { + let first = sourceCode.getFirstToken(node) + let last = sourceCode.getLastToken(node) + + // If the value enclosed by parentheses, update the 'first' and 'last' by the parentheses. + while (true) { + const prev = sourceCode.getTokenBefore(first) + const next = sourceCode.getTokenAfter(last) + if (this.isOpenParen(prev) && this.isCloseParen(next)) { + first = prev + last = next + } else { + return { first, last } + } + } } }, utils From 35ff4ef4179e225e8f5d74765c388df15722d23c Mon Sep 17 00:00:00 2001 From: "Xin Du (Clark)" Date: Tue, 12 Nov 2019 11:28:51 +0000 Subject: [PATCH 2/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5aa5006..b61f3e4 100644 --- a/README.md +++ b/README.md @@ -103,4 +103,4 @@ Include all the below rules, as well as all priority rules in above categories, | | Rule ID | Description | |:---|:--------|:------------| | :wrench: | [nuxt/no-timing-in-fetch-data](./docs/rules/no-timing-in-fetch-data.md) | Disallow `setTimeout/setInterval` in `asyncData/fetch` | -| :wrench: | [nuxt/require-func-head](./docs/rules/require-func-head.md) | Enforce `head` property in component to be a function. | +| | [nuxt/require-func-head](./docs/rules/require-func-head.md) | Enforce `head` property in component to be a function. | From 79403a0e917267fa20c59aae3ba1685f2266ed6b Mon Sep 17 00:00:00 2001 From: "Xin Du (Clark)" Date: Tue, 12 Nov 2019 11:29:08 +0000 Subject: [PATCH 3/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b61f3e4..0434e3b 100644 --- a/README.md +++ b/README.md @@ -102,5 +102,5 @@ Include all the below rules, as well as all priority rules in above categories, | | Rule ID | Description | |:---|:--------|:------------| -| :wrench: | [nuxt/no-timing-in-fetch-data](./docs/rules/no-timing-in-fetch-data.md) | Disallow `setTimeout/setInterval` in `asyncData/fetch` | +| | [nuxt/no-timing-in-fetch-data](./docs/rules/no-timing-in-fetch-data.md) | Disallow `setTimeout/setInterval` in `asyncData/fetch` | | | [nuxt/require-func-head](./docs/rules/require-func-head.md) | Enforce `head` property in component to be a function. |