ajv/lib/compile/util.js

214 lines
5.4 KiB
JavaScript
Raw Normal View History

'use strict';
module.exports = {
copy: copy,
deepClone: deepClone,
checkDataType: checkDataType,
checkDataTypes: checkDataTypes,
toHash: toHash,
getProperty: getProperty,
escapeQuotes: escapeQuotes,
ucs2length: ucs2length,
varOccurences: varOccurences,
varReplace: varReplace,
cleanUpCode: cleanUpCode,
cleanUpVarErrors: cleanUpVarErrors,
schemaHasRules: schemaHasRules,
2015-08-22 01:26:13 +03:00
stableStringify: require('json-stable-stringify'),
toQuotedString: toQuotedString,
getPathExpr: getPathExpr,
2015-12-19 13:52:39 +03:00
getPath: getPath,
unescapeFragment: unescapeFragment,
escapeFragment: escapeFragment
};
function copy(o, to) {
to = to || {};
for (var key in o) to[key] = o[key];
return to;
}
function deepClone(o) {
if (typeof o != 'object' || o === null) return o;
var res;
if (Array.isArray(o)) {
res = [];
for (var i=0; i<o.length; i++)
res[i] = deepClone(o[i]);
} else {
res = {};
for (var key in o)
res[key] = deepClone(o[key]);
}
return res;
}
function checkDataType(dataType, data, negate) {
var EQUAL = negate ? ' !== ' : ' === '
, AND = negate ? ' || ' : ' && '
, OK = negate ? '!' : ''
, NOT = negate ? '' : '!';
switch (dataType) {
case 'null': return data + EQUAL + 'null';
case 'array': return OK + 'Array.isArray(' + data + ')';
2015-08-22 01:26:13 +03:00
case 'object': return '(' + OK + data + AND +
'typeof ' + data + EQUAL + '"object"' + AND +
NOT + 'Array.isArray(' + data + '))';
2015-08-22 01:26:13 +03:00
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
2015-08-11 21:24:31 +03:00
NOT + '(' + data + ' % 1))';
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
}
}
2015-10-16 21:37:43 +03:00
function checkDataTypes(dataTypes, data) {
switch (dataTypes.length) {
2015-10-16 21:37:43 +03:00
case 1: return checkDataType(dataTypes[0], data, true);
default:
2015-08-11 21:24:31 +03:00
var code = '';
var types = toHash(dataTypes);
if (types.array && types.object) {
2015-10-16 21:37:43 +03:00
code = types.null ? '(': '(!' + data + ' || ';
code += 'typeof ' + data + ' !== "object")';
delete types.null;
delete types.array;
delete types.object;
}
if (types.number) delete types.integer;
for (var t in types)
2015-10-16 21:37:43 +03:00
code += (code ? ' && ' : '' ) + checkDataType(t, data, true);
return code;
}
}
2015-08-12 00:52:34 +03:00
function toHash(arr) {
var hash = {};
2015-08-12 00:52:34 +03:00
for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
return hash;
}
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
2015-08-22 01:26:13 +03:00
var SINGLE_QUOTE = /'|\\/g;
function getProperty(key) {
return IDENTIFIER.test(key)
? '.' + key
2015-08-22 01:26:13 +03:00
: "['" + key.replace(SINGLE_QUOTE, '\\$&') + "']";
}
function escapeQuotes(str) {
2015-08-22 01:26:13 +03:00
return str.replace(SINGLE_QUOTE, '\\$&');
}
// https://mathiasbynens.be/notes/javascript-encoding
// https://github.com/bestiejs/punycode.js - punycode.ucs2.decode
function ucs2length(str) {
var length = 0
, len = str.length
, pos = 0
, value;
while (pos < len) {
length++;
value = str.charCodeAt(pos++);
if (value >= 0xD800 && value <= 0xDBFF && pos < len) {
// high surrogate, and there is a next character
value = str.charCodeAt(pos);
if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate
}
}
return length;
}
function varOccurences(str, dataVar) {
dataVar += '[^0-9]';
var matches = str.match(new RegExp(dataVar, 'g'));
return matches ? matches.length : 0;
}
function varReplace(str, dataVar, expr) {
dataVar += '([^0-9])';
expr = expr.replace(/\$/g, '$$$$');
2015-08-11 21:24:31 +03:00
return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
}
var EMPTY_ELSE = /else\s*{\s*}/g
, EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
, EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
function cleanUpCode(out) {
return out.replace(EMPTY_ELSE, '')
.replace(EMPTY_IF_NO_ELSE, '')
.replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
}
var ERRORS_REGEXP = /[^v\.]errors/g
, REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
, RETURN_VALID = 'return errors === 0;'
, RETURN_TRUE = 'validate.errors = null; return true;';
2015-08-22 01:26:13 +03:00
function cleanUpVarErrors(out) {
var matches = out.match(ERRORS_REGEXP);
if (matches && matches.length === 2)
return out.replace(REMOVE_ERRORS, '')
.replace(RETURN_VALID, RETURN_TRUE);
else
return out;
}
function schemaHasRules(schema, rules) {
for (var key in schema) if (rules[key]) return true;
}
2015-08-22 01:26:13 +03:00
2015-08-22 01:26:13 +03:00
function toQuotedString (str) {
return '\'' + escapeQuotes(str) + '\'';
}
function getPathExpr (currentPath, expr, jsonPointers, isNumber) {
2015-12-19 13:52:39 +03:00
var path = jsonPointers // false by default
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
return joinPaths(currentPath, path);
2015-08-22 01:26:13 +03:00
}
function getPath (currentPath, prop, jsonPointers) {
2015-12-19 13:52:39 +03:00
var path = jsonPointers // false by default
? toQuotedString('/' + escapeFragment(prop))
: toQuotedString(getProperty(prop));
return joinPaths(currentPath, path);
2015-08-22 01:26:13 +03:00
}
function joinPaths (a, b) {
if (a == '""') return b;
2015-08-22 01:26:13 +03:00
return (a + ' + ' + b).replace(/' \+ '/g, '');
}
2015-12-19 13:52:39 +03:00
function unescapeFragment(str) {
return decodeURIComponent(str)
.replace(/~1/g, '/')
.replace(/~0/g, '~');
}
function escapeFragment(str) {
str = str.replace(/~/g, '~0').replace(/\//g, '~1');
return encodeURIComponent(str);
}