UNPKG

7.38 kBPlain TextView Raw
1import { isObservableArray, isObservableObject, isObservableMap, untracked } from "mobx"
2
3// Copied from React.PropTypes
4function createChainableTypeChecker(validator: React.Validator<any>): React.Requireable<any> {
5 function checkType(
6 isRequired: boolean,
7 props: any,
8 propName: string,
9 componentName: string,
10 location: string,
11 propFullName: string,
12 ...rest: any[]
13 ) {
14 return untracked(() => {
15 componentName = componentName || "<<anonymous>>"
16 propFullName = propFullName || propName
17 if (props[propName] == null) {
18 if (isRequired) {
19 const actual = props[propName] === null ? "null" : "undefined"
20 return new Error(
21 "The " +
22 location +
23 " `" +
24 propFullName +
25 "` is marked as required " +
26 "in `" +
27 componentName +
28 "`, but its value is `" +
29 actual +
30 "`."
31 )
32 }
33 return null
34 } else {
35 // @ts-ignore rest arg is necessary for some React internals - fails tests otherwise
36 return validator(props, propName, componentName, location, propFullName, ...rest)
37 }
38 })
39 }
40
41 const chainedCheckType: any = checkType.bind(null, false)
42 // Add isRequired to satisfy Requirable
43 chainedCheckType.isRequired = checkType.bind(null, true)
44 return chainedCheckType
45}
46
47// Copied from React.PropTypes
48function isSymbol(propType: any, propValue: any): boolean {
49 // Native Symbol.
50 if (propType === "symbol") {
51 return true
52 }
53
54 // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
55 if (propValue["@@toStringTag"] === "Symbol") {
56 return true
57 }
58
59 // Fallback for non-spec compliant Symbols which are polyfilled.
60 if (typeof Symbol === "function" && propValue instanceof Symbol) {
61 return true
62 }
63
64 return false
65}
66
67// Copied from React.PropTypes
68function getPropType(propValue: any): string {
69 const propType = typeof propValue
70 if (Array.isArray(propValue)) {
71 return "array"
72 }
73 if (propValue instanceof RegExp) {
74 // Old webkits (at least until Android 4.0) return 'function' rather than
75 // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
76 // passes PropTypes.object.
77 return "object"
78 }
79 if (isSymbol(propType, propValue)) {
80 return "symbol"
81 }
82 return propType
83}
84
85// This handles more types than `getPropType`. Only used for error messages.
86// Copied from React.PropTypes
87function getPreciseType(propValue: any): string {
88 const propType = getPropType(propValue)
89 if (propType === "object") {
90 if (propValue instanceof Date) {
91 return "date"
92 } else if (propValue instanceof RegExp) {
93 return "regexp"
94 }
95 }
96 return propType
97}
98
99function createObservableTypeCheckerCreator(
100 allowNativeType: any,
101 mobxType: any
102): React.Requireable<any> {
103 return createChainableTypeChecker((props, propName, componentName, location, propFullName) => {
104 return untracked(() => {
105 if (allowNativeType) {
106 if (getPropType(props[propName]) === mobxType.toLowerCase()) return null
107 }
108 let mobxChecker
109 switch (mobxType) {
110 case "Array":
111 mobxChecker = isObservableArray
112 break
113 case "Object":
114 mobxChecker = isObservableObject
115 break
116 case "Map":
117 mobxChecker = isObservableMap
118 break
119 default:
120 throw new Error(`Unexpected mobxType: ${mobxType}`)
121 }
122 const propValue = props[propName]
123 if (!mobxChecker(propValue)) {
124 const preciseType = getPreciseType(propValue)
125 const nativeTypeExpectationMessage = allowNativeType
126 ? " or javascript `" + mobxType.toLowerCase() + "`"
127 : ""
128 return new Error(
129 "Invalid prop `" +
130 propFullName +
131 "` of type `" +
132 preciseType +
133 "` supplied to" +
134 " `" +
135 componentName +
136 "`, expected `mobx.Observable" +
137 mobxType +
138 "`" +
139 nativeTypeExpectationMessage +
140 "."
141 )
142 }
143 return null
144 })
145 })
146}
147
148function createObservableArrayOfTypeChecker(
149 allowNativeType: boolean,
150 typeChecker: React.Validator<any>
151) {
152 return createChainableTypeChecker(
153 (props, propName, componentName, location, propFullName, ...rest) => {
154 return untracked(() => {
155 if (typeof typeChecker !== "function") {
156 return new Error(
157 "Property `" +
158 propFullName +
159 "` of component `" +
160 componentName +
161 "` has " +
162 "invalid PropType notation."
163 )
164 } else {
165 let error = createObservableTypeCheckerCreator(allowNativeType, "Array")(
166 props,
167 propName,
168 componentName,
169 location,
170 propFullName
171 )
172
173 if (error instanceof Error) return error
174 const propValue = props[propName]
175 for (let i = 0; i < propValue.length; i++) {
176 error = (typeChecker as React.Validator<any>)(
177 propValue,
178 i as any,
179 componentName,
180 location,
181 propFullName + "[" + i + "]",
182 ...rest
183 )
184 if (error instanceof Error) return error
185 }
186
187 return null
188 }
189 })
190 }
191 )
192}
193
194const observableArray = createObservableTypeCheckerCreator(false, "Array")
195const observableArrayOf = createObservableArrayOfTypeChecker.bind(null, false)
196const observableMap = createObservableTypeCheckerCreator(false, "Map")
197const observableObject = createObservableTypeCheckerCreator(false, "Object")
198const arrayOrObservableArray = createObservableTypeCheckerCreator(true, "Array")
199const arrayOrObservableArrayOf = createObservableArrayOfTypeChecker.bind(null, true)
200const objectOrObservableObject = createObservableTypeCheckerCreator(true, "Object")
201
202export const PropTypes = {
203 observableArray,
204 observableArrayOf,
205 observableMap,
206 observableObject,
207 arrayOrObservableArray,
208 arrayOrObservableArrayOf,
209 objectOrObservableObject
210}