Skip to content
Permalink
Newer
Older
100644 239 lines (192 sloc) 6.81 KB
Ignoring revisions in .git-blame-ignore-revs.
September 14, 2020 10:42
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
schemaHasRules: schemaHasRules,
17
schemaHasRulesExcept: schemaHasRulesExcept,
18
schemaUnknownRules: schemaUnknownRules,
19
toQuotedString: toQuotedString,
20
getPathExpr: getPathExpr,
21
getPath: getPath,
22
getData: getData,
23
unescapeFragment: unescapeFragment,
24
unescapeJsonPointer: unescapeJsonPointer,
25
escapeFragment: escapeFragment,
26
escapeJsonPointer: escapeJsonPointer
27
};
28
29
30
function copy(o, to) {
31
to = to || {};
32
for (var key in o) to[key] = o[key];
33
return to;
34
}
35
36
37
function checkDataType(dataType, data, strictNumbers, negate) {
38
var EQUAL = negate ? ' !== ' : ' === '
39
, AND = negate ? ' || ' : ' && '
40
, OK = negate ? '!' : ''
41
, NOT = negate ? '' : '!';
42
switch (dataType) {
43
case 'null': return data + EQUAL + 'null';
44
case 'array': return OK + 'Array.isArray(' + data + ')';
45
case 'object': return '(' + OK + data + AND +
46
'typeof ' + data + EQUAL + '"object"' + AND +
47
NOT + 'Array.isArray(' + data + '))';
48
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
49
NOT + '(' + data + ' % 1)' +
50
AND + data + EQUAL + data +
51
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')';
52
case 'number': return '(typeof ' + data + EQUAL + '"' + dataType + '"' +
53
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')';
54
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
55
}
56
}
57
58
59
function checkDataTypes(dataTypes, data, strictNumbers) {
60
switch (dataTypes.length) {
61
case 1: return checkDataType(dataTypes[0], data, strictNumbers, true);
62
default:
63
var code = '';
64
var types = toHash(dataTypes);
65
if (types.array && types.object) {
66
code = types.null ? '(': '(!' + data + ' || ';
67
code += 'typeof ' + data + ' !== "object")';
68
delete types.null;
69
delete types.array;
70
delete types.object;
71
}
72
if (types.number) delete types.integer;
73
for (var t in types)
74
code += (code ? ' && ' : '' ) + checkDataType(t, data, strictNumbers, true);
75
76
return code;
77
}
78
}
79
80
81
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
82
function coerceToTypes(optionCoerceTypes, dataTypes) {
83
if (Array.isArray(dataTypes)) {
84
var types = [];
85
for (var i=0; i<dataTypes.length; i++) {
86
var t = dataTypes[i];
87
if (COERCE_TO_TYPES[t]) types[types.length] = t;
88
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
89
}
90
if (types.length) return types;
91
} else if (COERCE_TO_TYPES[dataTypes]) {
92
return [dataTypes];
93
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
94
return ['array'];
95
}
96
}
97
98
99
function toHash(arr) {
100
var hash = {};
101
for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
102
return hash;
103
}
104
105
106
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
107
var SINGLE_QUOTE = /'|\\/g;
108
function getProperty(key) {
109
return typeof key == 'number'
110
? '[' + key + ']'
111
: IDENTIFIER.test(key)
112
? '.' + key
113
: "['" + escapeQuotes(key) + "']";
114
}
115
116
117
function escapeQuotes(str) {
118
return str.replace(SINGLE_QUOTE, '\\$&')
119
.replace(/\n/g, '\\n')
120
.replace(/\r/g, '\\r')
121
.replace(/\f/g, '\\f')
122
.replace(/\t/g, '\\t');
123
}
124
125
126
function varOccurences(str, dataVar) {
127
dataVar += '[^0-9]';
128
var matches = str.match(new RegExp(dataVar, 'g'));
129
return matches ? matches.length : 0;
130
}
131
132
133
function varReplace(str, dataVar, expr) {
134
dataVar += '([^0-9])';
135
expr = expr.replace(/\$/g, '$$$$');
136
return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
137
}
138
139
140
function schemaHasRules(schema, rules) {
141
if (typeof schema == 'boolean') return !schema;
142
for (var key in schema) if (rules[key]) return true;
143
}
144
145
146
function schemaHasRulesExcept(schema, rules, exceptKeyword) {
147
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
148
for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
149
}
150
151
152
function schemaUnknownRules(schema, rules) {
153
if (typeof schema == 'boolean') return;
154
for (var key in schema) if (!rules[key]) return key;
155
}
156
157
158
function toQuotedString(str) {
159
return '\'' + escapeQuotes(str) + '\'';
160
}
161
162
163
function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
164
var path = jsonPointers // false by default
165
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
166
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
167
return joinPaths(currentPath, path);
168
}
169
170
171
function getPath(currentPath, prop, jsonPointers) {
172
var path = jsonPointers // false by default
173
? toQuotedString('/' + escapeJsonPointer(prop))
174
: toQuotedString(getProperty(prop));
175
return joinPaths(currentPath, path);
176
}
177
178
179
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
180
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
181
function getData($data, lvl, paths) {
182
var up, jsonPointer, data, matches;
183
if ($data === '') return 'rootData';
184
if ($data[0] == '/') {
185
if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data);
186
jsonPointer = $data;
187
data = 'rootData';
188
} else {
189
matches = $data.match(RELATIVE_JSON_POINTER);
190
if (!matches) throw new Error('Invalid JSON-pointer: ' + $data);
191
up = +matches[1];
192
jsonPointer = matches[2];
193
if (jsonPointer == '#') {
194
if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
195
return paths[lvl - up];
196
}
197
198
if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
199
data = 'data' + ((lvl - up) || '');
200
if (!jsonPointer) return data;
201
}
202
203
var expr = data;
204
var segments = jsonPointer.split('/');
205
for (var i=0; i<segments.length; i++) {
206
var segment = segments[i];
207
if (segment) {
208
data += getProperty(unescapeJsonPointer(segment));
209
expr += ' && ' + data;
210
}
211
}
212
return expr;
213
}
214
215
216
function joinPaths (a, b) {
217
if (a == '""') return b;
218
return (a + ' + ' + b).replace(/([^\\])' \+ '/g, '$1');
219
}
220
221
222
function unescapeFragment(str) {
223
return unescapeJsonPointer(decodeURIComponent(str));
224
}
225
226
227
function escapeFragment(str) {
228
return encodeURIComponent(escapeJsonPointer(str));
229
}
230
231
232
function escapeJsonPointer(str) {
233
return str.replace(/~/g, '~0').replace(/\//g, '~1');
234
}
235
236
237
function unescapeJsonPointer(str) {
238
return str.replace(/~1/g, '/').replace(/~0/g, '~');
239
}