util.js (8083B)
1 'use strict'; 2 3 4 module.exports = { 5 copy: copy, 6 checkDataType: checkDataType, 7 checkDataTypes: checkDataTypes, 8 coerceToTypes: coerceToTypes, 9 toHash: toHash, 10 getProperty: getProperty, 11 escapeQuotes: escapeQuotes, 12 equal: require('fast-deep-equal'), 13 ucs2length: require('./ucs2length'), 14 varOccurences: varOccurences, 15 varReplace: varReplace, 16 cleanUpCode: cleanUpCode, 17 finalCleanUpCode: finalCleanUpCode, 18 schemaHasRules: schemaHasRules, 19 schemaHasRulesExcept: schemaHasRulesExcept, 20 schemaUnknownRules: schemaUnknownRules, 21 toQuotedString: toQuotedString, 22 getPathExpr: getPathExpr, 23 getPath: getPath, 24 getData: getData, 25 unescapeFragment: unescapeFragment, 26 unescapeJsonPointer: unescapeJsonPointer, 27 escapeFragment: escapeFragment, 28 escapeJsonPointer: escapeJsonPointer 29 }; 30 31 32 function copy(o, to) { 33 to = to || {}; 34 for (var key in o) to[key] = o[key]; 35 return to; 36 } 37 38 39 function checkDataType(dataType, data, negate) { 40 var EQUAL = negate ? ' !== ' : ' === ' 41 , AND = negate ? ' || ' : ' && ' 42 , OK = negate ? '!' : '' 43 , NOT = negate ? '' : '!'; 44 switch (dataType) { 45 case 'null': return data + EQUAL + 'null'; 46 case 'array': return OK + 'Array.isArray(' + data + ')'; 47 case 'object': return '(' + OK + data + AND + 48 'typeof ' + data + EQUAL + '"object"' + AND + 49 NOT + 'Array.isArray(' + data + '))'; 50 case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + 51 NOT + '(' + data + ' % 1)' + 52 AND + data + EQUAL + data + ')'; 53 default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; 54 } 55 } 56 57 58 function checkDataTypes(dataTypes, data) { 59 switch (dataTypes.length) { 60 case 1: return checkDataType(dataTypes[0], data, true); 61 default: 62 var code = ''; 63 var types = toHash(dataTypes); 64 if (types.array && types.object) { 65 code = types.null ? '(': '(!' + data + ' || '; 66 code += 'typeof ' + data + ' !== "object")'; 67 delete types.null; 68 delete types.array; 69 delete types.object; 70 } 71 if (types.number) delete types.integer; 72 for (var t in types) 73 code += (code ? ' && ' : '' ) + checkDataType(t, data, true); 74 75 return code; 76 } 77 } 78 79 80 var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]); 81 function coerceToTypes(optionCoerceTypes, dataTypes) { 82 if (Array.isArray(dataTypes)) { 83 var types = []; 84 for (var i=0; i<dataTypes.length; i++) { 85 var t = dataTypes[i]; 86 if (COERCE_TO_TYPES[t]) types[types.length] = t; 87 else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t; 88 } 89 if (types.length) return types; 90 } else if (COERCE_TO_TYPES[dataTypes]) { 91 return [dataTypes]; 92 } else if (optionCoerceTypes === 'array' && dataTypes === 'array') { 93 return ['array']; 94 } 95 } 96 97 98 function toHash(arr) { 99 var hash = {}; 100 for (var i=0; i<arr.length; i++) hash[arr[i]] = true; 101 return hash; 102 } 103 104 105 var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; 106 var SINGLE_QUOTE = /'|\\/g; 107 function getProperty(key) { 108 return typeof key == 'number' 109 ? '[' + key + ']' 110 : IDENTIFIER.test(key) 111 ? '.' + key 112 : "['" + escapeQuotes(key) + "']"; 113 } 114 115 116 function escapeQuotes(str) { 117 return str.replace(SINGLE_QUOTE, '\\$&') 118 .replace(/\n/g, '\\n') 119 .replace(/\r/g, '\\r') 120 .replace(/\f/g, '\\f') 121 .replace(/\t/g, '\\t'); 122 } 123 124 125 function varOccurences(str, dataVar) { 126 dataVar += '[^0-9]'; 127 var matches = str.match(new RegExp(dataVar, 'g')); 128 return matches ? matches.length : 0; 129 } 130 131 132 function varReplace(str, dataVar, expr) { 133 dataVar += '([^0-9])'; 134 expr = expr.replace(/\$/g, '$$$$'); 135 return str.replace(new RegExp(dataVar, 'g'), expr + '$1'); 136 } 137 138 139 var EMPTY_ELSE = /else\s*{\s*}/g 140 , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g 141 , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g; 142 function cleanUpCode(out) { 143 return out.replace(EMPTY_ELSE, '') 144 .replace(EMPTY_IF_NO_ELSE, '') 145 .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))'); 146 } 147 148 149 var ERRORS_REGEXP = /[^v.]errors/g 150 , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g 151 , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g 152 , RETURN_VALID = 'return errors === 0;' 153 , RETURN_TRUE = 'validate.errors = null; return true;' 154 , RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/ 155 , RETURN_DATA_ASYNC = 'return data;' 156 , ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g 157 , REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/; 158 159 function finalCleanUpCode(out, async) { 160 var matches = out.match(ERRORS_REGEXP); 161 if (matches && matches.length == 2) { 162 out = async 163 ? out.replace(REMOVE_ERRORS_ASYNC, '') 164 .replace(RETURN_ASYNC, RETURN_DATA_ASYNC) 165 : out.replace(REMOVE_ERRORS, '') 166 .replace(RETURN_VALID, RETURN_TRUE); 167 } 168 169 matches = out.match(ROOTDATA_REGEXP); 170 if (!matches || matches.length !== 3) return out; 171 return out.replace(REMOVE_ROOTDATA, ''); 172 } 173 174 175 function schemaHasRules(schema, rules) { 176 if (typeof schema == 'boolean') return !schema; 177 for (var key in schema) if (rules[key]) return true; 178 } 179 180 181 function schemaHasRulesExcept(schema, rules, exceptKeyword) { 182 if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not'; 183 for (var key in schema) if (key != exceptKeyword && rules[key]) return true; 184 } 185 186 187 function schemaUnknownRules(schema, rules) { 188 if (typeof schema == 'boolean') return; 189 for (var key in schema) if (!rules[key]) return key; 190 } 191 192 193 function toQuotedString(str) { 194 return '\'' + escapeQuotes(str) + '\''; 195 } 196 197 198 function getPathExpr(currentPath, expr, jsonPointers, isNumber) { 199 var path = jsonPointers // false by default 200 ? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')') 201 : (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\''); 202 return joinPaths(currentPath, path); 203 } 204 205 206 function getPath(currentPath, prop, jsonPointers) { 207 var path = jsonPointers // false by default 208 ? toQuotedString('/' + escapeJsonPointer(prop)) 209 : toQuotedString(getProperty(prop)); 210 return joinPaths(currentPath, path); 211 } 212 213 214 var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; 215 var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; 216 function getData($data, lvl, paths) { 217 var up, jsonPointer, data, matches; 218 if ($data === '') return 'rootData'; 219 if ($data[0] == '/') { 220 if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data); 221 jsonPointer = $data; 222 data = 'rootData'; 223 } else { 224 matches = $data.match(RELATIVE_JSON_POINTER); 225 if (!matches) throw new Error('Invalid JSON-pointer: ' + $data); 226 up = +matches[1]; 227 jsonPointer = matches[2]; 228 if (jsonPointer == '#') { 229 if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); 230 return paths[lvl - up]; 231 } 232 233 if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); 234 data = 'data' + ((lvl - up) || ''); 235 if (!jsonPointer) return data; 236 } 237 238 var expr = data; 239 var segments = jsonPointer.split('/'); 240 for (var i=0; i<segments.length; i++) { 241 var segment = segments[i]; 242 if (segment) { 243 data += getProperty(unescapeJsonPointer(segment)); 244 expr += ' && ' + data; 245 } 246 } 247 return expr; 248 } 249 250 251 function joinPaths (a, b) { 252 if (a == '""') return b; 253 return (a + ' + ' + b).replace(/' \+ '/g, ''); 254 } 255 256 257 function unescapeFragment(str) { 258 return unescapeJsonPointer(decodeURIComponent(str)); 259 } 260 261 262 function escapeFragment(str) { 263 return encodeURIComponent(escapeJsonPointer(str)); 264 } 265 266 267 function escapeJsonPointer(str) { 268 return str.replace(/~/g, '~0').replace(/\//g, '~1'); 269 } 270 271 272 function unescapeJsonPointer(str) { 273 return str.replace(/~1/g, '/').replace(/~0/g, '~'); 274 }