1 |
|
2 |
|
3 | import { warn } from './debug'
|
4 | import { observe, toggleObserving, shouldObserve } from '../observer/index'
|
5 | import {
|
6 | hasOwn,
|
7 | isObject,
|
8 | toRawType,
|
9 | hyphenate,
|
10 | capitalize,
|
11 | isPlainObject
|
12 | } from 'shared/util'
|
13 |
|
14 | type PropOptions = {
|
15 | type: Function | Array<Function> | null,
|
16 | default: any,
|
17 | required: ?boolean,
|
18 | validator: ?Function
|
19 | };
|
20 |
|
21 | export 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 |
|
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 |
|
37 |
|
38 | const stringIndex = getTypeIndex(String, prop.type)
|
39 | if (stringIndex < 0 || booleanIndex < stringIndex) {
|
40 | value = true
|
41 | }
|
42 | }
|
43 | }
|
44 |
|
45 | if (value === undefined) {
|
46 | value = getPropDefaultValue(vm, prop, key)
|
47 |
|
48 |
|
49 | const prevShouldObserve = shouldObserve
|
50 | toggleObserving(true)
|
51 | observe(value)
|
52 | toggleObserving(prevShouldObserve)
|
53 | }
|
54 | if (
|
55 | process.env.NODE_ENV !== 'production' &&
|
56 |
|
57 | !(__WEEX__ && isObject(value) && ('@binding' in value))
|
58 | ) {
|
59 | assertProp(prop, key, value, vm, absent)
|
60 | }
|
61 | return value
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
|
68 |
|
69 | if (!hasOwn(prop, 'default')) {
|
70 | return undefined
|
71 | }
|
72 | const def = prop.default
|
73 |
|
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 |
|
83 |
|
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 |
|
91 |
|
92 | return typeof def === 'function' && getType(prop.type) !== 'Function'
|
93 | ? def.call(vm)
|
94 | : def
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | function 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 |
|
149 | const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
|
150 |
|
151 | function 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 |
|
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 |
|
179 |
|
180 |
|
181 |
|
182 | function getType (fn) {
|
183 | const match = fn && fn.toString().match(/^\s*function (\w+)/)
|
184 | return match ? match[1] : ''
|
185 | }
|
186 |
|
187 | function isSameType (a, b) {
|
188 | return getType(a) === getType(b)
|
189 | }
|
190 |
|
191 | function 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 |
|
203 | function 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 |
|
211 | if (expectedTypes.length === 1 &&
|
212 | isExplicable(expectedType) &&
|
213 | !isBoolean(expectedType, receivedType)) {
|
214 | message += ` with value ${expectedValue}`
|
215 | }
|
216 | message += `, got ${receivedType} `
|
217 |
|
218 | if (isExplicable(receivedType)) {
|
219 | message += `with value ${receivedValue}.`
|
220 | }
|
221 | return message
|
222 | }
|
223 |
|
224 | function 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 |
|
234 | function isExplicable (value) {
|
235 | const explicitTypes = ['string', 'number', 'boolean']
|
236 | return explicitTypes.some(elem => value.toLowerCase() === elem)
|
237 | }
|
238 |
|
239 | function isBoolean (...args) {
|
240 | return args.some(elem => elem.toLowerCase() === 'boolean')
|
241 | }
|