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

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(rule): add require-func-head in recommended (#62)
  • Loading branch information
clarkdo committed Nov 18, 2019
2 parents 8826a46 + 79403a0 commit 7d0926f
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 3 deletions.
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

0 comments on commit 7d0926f

Please sign in to comment.