Skip to content

Add rule vue/require-valid-default-prop. #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/rules/require-valid-default-prop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Enforces props default values to be valid (require-valid-default-prop)

This rule checks whether the default value of each prop is valid for the given type. It should report an error when default value for type `Array` or `Object` is not returned using function.

## :book: Rule Details

:-1: Examples of **incorrect** code for this rule:

```js
Vue.component('example', {
props: {
propA: {
type: String,
default: {}
},
propB: {
type: String,
default: []
},
propC: {
type: Object,
default: []
},
propD: {
type: Array,
default: []
},
propE: {
type: Object,
default: { message: 'hello' }
}
}
})
```

:+1: Examples of **correct** code for this rule:

```js
Vue.component('example', {
props: {
// basic type check (`null` means accept any type)
propA: Number,
// multiple possible types
propB: [String, Number],
// a number with default value
propD: {
type: Number,
default: 100
},
// object/array defaults should be returned from a factory function
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
}
}
})
```

## :wrench: Options

Nothing.
134 changes: 134 additions & 0 deletions lib/rules/require-valid-default-prop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @fileoverview Enforces props default values to be valid.
* @author Armano
*/
'use strict'
const utils = require('../utils')

const NATIVE_TYPES = new Set([
'String',
'Number',
'Boolean',
'Function',
'Object',
'Array',
'Symbol'
])

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Enforces props default values to be valid.',
category: 'Possible Errors',
recommended: false
},
fixable: null,
schema: []
},

create (context) {
// ----------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------

function isPropertyIdentifier (node) {
return node.type === 'Property' && node.key.type === 'Identifier'
}

function getPropertyNode (obj, name) {
return obj.properties.find(p =>
isPropertyIdentifier(p) &&
p.key.name === name
)
}

function getTypes (node) {
if (node.type === 'Identifier') {
return [node.name]
} else if (node.type === 'ArrayExpression') {
return node.elements
.filter(item => item.type === 'Identifier')
.map(item => item.name)
}
return []
}

function ucFirst (text) {
return text[0].toUpperCase() + text.slice(1)
}

function getValueType (node) {
if (node.type === 'CallExpression') { // Symbol(), Number() ...
if (node.callee.type === 'Identifier' && NATIVE_TYPES.has(node.callee.name)) {
return node.callee.name
}
} else if (node.type === 'TemplateLiteral') { // String
return 'String'
} else if (node.type === 'Literal') { // String, Boolean, Number
if (node.value === null) return null
const type = ucFirst(typeof node.value)
if (NATIVE_TYPES.has(type)) {
return type
}
} else if (node.type === 'ArrayExpression') { // Array
return 'Array'
} else if (node.type === 'ObjectExpression') { // Object
return 'Object'
}
// FunctionExpression, ArrowFunctionExpression
return null
}

// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------

return utils.executeOnVue(context, obj => {
const props = obj.properties.find(p =>
isPropertyIdentifier(p) &&
p.key.name === 'props' &&
p.value.type === 'ObjectExpression'
)
if (!props) return

const properties = props.value.properties.filter(p =>
isPropertyIdentifier(p) &&
p.value.type === 'ObjectExpression'
)

for (const prop of properties) {
const type = getPropertyNode(prop.value, 'type')
if (!type) {
return
}

const typeNames = new Set(getTypes(type.value)
.map(item => item === 'Object' || item === 'Array' ? 'Function' : item) // Object and Array require function
.filter(item => NATIVE_TYPES.has(item)))

if (typeNames.size === 0) { // There is no native types detected
return
}

const def = getPropertyNode(prop.value, 'default')
if (!def) return

const defType = getValueType(def.value)
if (typeNames.has(defType)) return

context.report({
node: def,
message: "Type of the default value for '{{name}}' prop must be a {{types}}.",
data: {
name: prop.key.name,
types: Array.from(typeNames).join(' or ').toLowerCase()
}
})
}
})
}
}
Loading