Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

feat(rule): add require-func-head in recommended #62

Merged
merged 3 commits into from Nov 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -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` |
| | [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. |
40 changes: 40 additions & 0 deletions 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)
3 changes: 2 additions & 1 deletion 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'
}
}
3 changes: 2 additions & 1 deletion lib/index.js
Expand Up @@ -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'),
Expand Down
60 changes: 60 additions & 0 deletions lib/rules/__tests__/require-func-head.test.js
@@ -0,0 +1,60 @@
/**
* @fileoverview disallow `setTimeout/setInterval` in `asyncData/fetch`
* @author Xin Du <clark.duxin@gmail.com>
*/
'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
}
]
})
54 changes: 54 additions & 0 deletions lib/rules/require-func-head.js
@@ -0,0 +1,54 @@
/**
* @fileoverview enforce component's head property to be a function.
* @author Xin Du <clark.duxin@gmail.com>
*/
'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}')
]
}
})
})
})
}
}
22 changes: 22 additions & 0 deletions lib/utils/index.js
Expand Up @@ -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
Expand Down