UNPKG

6.31 kBJavaScriptView Raw
1/* @flow */
2
3import { warn } from './debug'
4import { observe, toggleObserving, shouldObserve } from '../observer/index'
5import {
6 hasOwn,
7 isObject,
8 toRawType,
9 hyphenate,
10 capitalize,
11 isPlainObject
12} from 'shared/util'
13
14type PropOptions = {
15 type: Function | Array<Function> | null,
16 default: any,
17 required: ?boolean,
18 validator: ?Function
19};
20
21export function validateProp (
22 key: string,
23 propOptions: Object,
24 propsData: Object,
25 vm?: Component
26): any {
27 const prop = propOptions[key]
28 const absent = !hasOwn(propsData, key)
29 let value = propsData[key]
30 // boolean casting
31 const booleanIndex = getTypeIndex(Boolean, prop.type)
32 if (booleanIndex > -1) {
33 if (absent && !hasOwn(prop, 'default')) {
34 value = false
35 } else if (value === '' || value === hyphenate(key)) {
36 // only cast empty string / same name to boolean if
37 // boolean has higher priority
38 const stringIndex = getTypeIndex(String, prop.type)
39 if (stringIndex < 0 || booleanIndex < stringIndex) {
40 value = true
41 }
42 }
43 }
44 // check default value
45 if (value === undefined) {
46 value = getPropDefaultValue(vm, prop, key)
47 // since the default value is a fresh copy,
48 // make sure to observe it.
49 const prevShouldObserve = shouldObserve
50 toggleObserving(true)
51 observe(value)
52 toggleObserving(prevShouldObserve)
53 }
54 if (
55 process.env.NODE_ENV !== 'production' &&
56 // skip validation for weex recycle-list child component props
57 !(__WEEX__ && isObject(value) && ('@binding' in value))
58 ) {
59 assertProp(prop, key, value, vm, absent)
60 }
61 return value
62}
63
64/**
65 * Get the default value of a prop.
66 */
67function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
68 // no default, return undefined
69 if (!hasOwn(prop, 'default')) {
70 return undefined
71 }
72 const def = prop.default
73 // warn against non-factory defaults for Object & Array
74 if (process.env.NODE_ENV !== 'production' && isObject(def)) {
75 warn(
76 'Invalid default value for prop "' + key + '": ' +
77 'Props with type Object/Array must use a factory function ' +
78 'to return the default value.',
79 vm
80 )
81 }
82 // the raw prop value was also undefined from previous render,
83 // return previous default value to avoid unnecessary watcher trigger
84 if (vm && vm.$options.propsData &&
85 vm.$options.propsData[key] === undefined &&
86 vm._props[key] !== undefined
87 ) {
88 return vm._props[key]
89 }
90 // call factory function for non-Function types
91 // a value is Function if its prototype is function even across different execution context
92 return typeof def === 'function' && getType(prop.type) !== 'Function'
93 ? def.call(vm)
94 : def
95}
96
97/**
98 * Assert whether a prop is valid.
99 */
100function assertProp (
101 prop: PropOptions,
102 name: string,
103 value: any,
104 vm: ?Component,
105 absent: boolean
106) {
107 if (prop.required && absent) {
108 warn(
109 'Missing required prop: "' + name + '"',
110 vm
111 )
112 return
113 }
114 if (value == null && !prop.required) {
115 return
116 }
117 let type = prop.type
118 let valid = !type || type === true
119 const expectedTypes = []
120 if (type) {
121 if (!Array.isArray(type)) {
122 type = [type]
123 }
124 for (let i = 0; i < type.length && !valid; i++) {
125 const assertedType = assertType(value, type[i])
126 expectedTypes.push(assertedType.expectedType || '')
127 valid = assertedType.valid
128 }
129 }
130
131 if (!valid) {
132 warn(
133 getInvalidTypeMessage(name, value, expectedTypes),
134 vm
135 )
136 return
137 }
138 const validator = prop.validator
139 if (validator) {
140 if (!validator(value)) {
141 warn(
142 'Invalid prop: custom validator check failed for prop "' + name + '".',
143 vm
144 )
145 }
146 }
147}
148
149const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
150
151function assertType (value: any, type: Function): {
152 valid: boolean;
153 expectedType: string;
154} {
155 let valid
156 const expectedType = getType(type)
157 if (simpleCheckRE.test(expectedType)) {
158 const t = typeof value
159 valid = t === expectedType.toLowerCase()
160 // for primitive wrapper objects
161 if (!valid && t === 'object') {
162 valid = value instanceof type
163 }
164 } else if (expectedType === 'Object') {
165 valid = isPlainObject(value)
166 } else if (expectedType === 'Array') {
167 valid = Array.isArray(value)
168 } else {
169 valid = value instanceof type
170 }
171 return {
172 valid,
173 expectedType
174 }
175}
176
177/**
178 * Use function string name to check built-in types,
179 * because a simple equality check will fail when running
180 * across different vms / iframes.
181 */
182function getType (fn) {
183 const match = fn && fn.toString().match(/^\s*function (\w+)/)
184 return match ? match[1] : ''
185}
186
187function isSameType (a, b) {
188 return getType(a) === getType(b)
189}
190
191function getTypeIndex (type, expectedTypes): number {
192 if (!Array.isArray(expectedTypes)) {
193 return isSameType(expectedTypes, type) ? 0 : -1
194 }
195 for (let i = 0, len = expectedTypes.length; i < len; i++) {
196 if (isSameType(expectedTypes[i], type)) {
197 return i
198 }
199 }
200 return -1
201}
202
203function getInvalidTypeMessage (name, value, expectedTypes) {
204 let message = `Invalid prop: type check failed for prop "${name}".` +
205 ` Expected ${expectedTypes.map(capitalize).join(', ')}`
206 const expectedType = expectedTypes[0]
207 const receivedType = toRawType(value)
208 const expectedValue = styleValue(value, expectedType)
209 const receivedValue = styleValue(value, receivedType)
210 // check if we need to specify expected value
211 if (expectedTypes.length === 1 &&
212 isExplicable(expectedType) &&
213 !isBoolean(expectedType, receivedType)) {
214 message += ` with value ${expectedValue}`
215 }
216 message += `, got ${receivedType} `
217 // check if we need to specify received value
218 if (isExplicable(receivedType)) {
219 message += `with value ${receivedValue}.`
220 }
221 return message
222}
223
224function styleValue (value, type) {
225 if (type === 'String') {
226 return `"${value}"`
227 } else if (type === 'Number') {
228 return `${Number(value)}`
229 } else {
230 return `${value}`
231 }
232}
233
234function isExplicable (value) {
235 const explicitTypes = ['string', 'number', 'boolean']
236 return explicitTypes.some(elem => value.toLowerCase() === elem)
237}
238
239function isBoolean (...args) {
240 return args.some(elem => elem.toLowerCase() === 'boolean')
241}