Initial commit
[yaffs-website] / node_modules / ajv / lib / compile / index.js
1 'use strict';
2
3 var resolve = require('./resolve')
4   , util = require('./util')
5   , stableStringify = require('json-stable-stringify')
6   , async = require('../async');
7
8 var beautify;
9
10 function loadBeautify(){
11   if (beautify === undefined) {
12     var name = 'js-beautify';
13     try { beautify = require(name).js_beautify; }
14     catch(e) { beautify = false; }
15   }
16 }
17
18 var validateGenerator = require('../dotjs/validate');
19
20 /**
21  * Functions below are used inside compiled validations function
22  */
23
24 var co = require('co');
25 var ucs2length = util.ucs2length;
26 var equal = require('./equal');
27
28 // this error is thrown by async schemas to return validation errors via exception
29 var ValidationError = require('./validation_error');
30
31 module.exports = compile;
32
33
34 /**
35  * Compiles schema to validation function
36  * @this   Ajv
37  * @param  {Object} schema schema object
38  * @param  {Object} root object with information about the root schema for this schema
39  * @param  {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution
40  * @param  {String} baseId base ID for IDs in the schema
41  * @return {Function} validation function
42  */
43 function compile(schema, root, localRefs, baseId) {
44   /* jshint validthis: true, evil: true */
45   /* eslint no-shadow: 0 */
46   var self = this
47     , opts = this._opts
48     , refVal = [ undefined ]
49     , refs = {}
50     , patterns = []
51     , patternsHash = {}
52     , defaults = []
53     , defaultsHash = {}
54     , customRules = []
55     , keepSourceCode = opts.sourceCode !== false;
56
57   root = root || { schema: schema, refVal: refVal, refs: refs };
58
59   var c = checkCompiling.call(this, schema, root, baseId);
60   var compilation = this._compilations[c.index];
61   if (c.compiling) return (compilation.callValidate = callValidate);
62
63   var formats = this._formats;
64   var RULES = this.RULES;
65
66   try {
67     var v = localCompile(schema, root, localRefs, baseId);
68     compilation.validate = v;
69     var cv = compilation.callValidate;
70     if (cv) {
71       cv.schema = v.schema;
72       cv.errors = null;
73       cv.refs = v.refs;
74       cv.refVal = v.refVal;
75       cv.root = v.root;
76       cv.$async = v.$async;
77       if (keepSourceCode) cv.sourceCode = v.sourceCode;
78     }
79     return v;
80   } finally {
81     endCompiling.call(this, schema, root, baseId);
82   }
83
84   function callValidate() {
85     var validate = compilation.validate;
86     var result = validate.apply(null, arguments);
87     callValidate.errors = validate.errors;
88     return result;
89   }
90
91   function localCompile(_schema, _root, localRefs, baseId) {
92     var isRoot = !_root || (_root && _root.schema == _schema);
93     if (_root.schema != root.schema)
94       return compile.call(self, _schema, _root, localRefs, baseId);
95
96     var $async = _schema.$async === true;
97     if ($async && !opts.transpile) async.setup(opts);
98
99     var sourceCode = validateGenerator({
100       isTop: true,
101       schema: _schema,
102       isRoot: isRoot,
103       baseId: baseId,
104       root: _root,
105       schemaPath: '',
106       errSchemaPath: '#',
107       errorPath: '""',
108       RULES: RULES,
109       validate: validateGenerator,
110       util: util,
111       resolve: resolve,
112       resolveRef: resolveRef,
113       usePattern: usePattern,
114       useDefault: useDefault,
115       useCustomRule: useCustomRule,
116       opts: opts,
117       formats: formats,
118       self: self
119     });
120
121     sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode)
122                    + vars(defaults, defaultCode) + vars(customRules, customRuleCode)
123                    + sourceCode;
124
125     if (opts.beautify) {
126       loadBeautify();
127       /* istanbul ignore else */
128       if (beautify) sourceCode = beautify(sourceCode, opts.beautify);
129       else console.error('"npm install js-beautify" to use beautify option');
130     }
131     // console.log('\n\n\n *** \n', sourceCode);
132     var validate, validateCode
133       , transpile = opts._transpileFunc;
134     try {
135       validateCode = $async && transpile
136                       ? transpile(sourceCode)
137                       : sourceCode;
138
139       var makeValidate = new Function(
140         'self',
141         'RULES',
142         'formats',
143         'root',
144         'refVal',
145         'defaults',
146         'customRules',
147         'co',
148         'equal',
149         'ucs2length',
150         'ValidationError',
151         validateCode
152       );
153
154       validate = makeValidate(
155         self,
156         RULES,
157         formats,
158         root,
159         refVal,
160         defaults,
161         customRules,
162         co,
163         equal,
164         ucs2length,
165         ValidationError
166       );
167
168       refVal[0] = validate;
169     } catch(e) {
170       console.error('Error compiling schema, function code:', validateCode);
171       throw e;
172     }
173
174     validate.schema = _schema;
175     validate.errors = null;
176     validate.refs = refs;
177     validate.refVal = refVal;
178     validate.root = isRoot ? validate : _root;
179     if ($async) validate.$async = true;
180     if (keepSourceCode) validate.sourceCode = sourceCode;
181     if (opts.sourceCode === true) {
182       validate.source = {
183         patterns: patterns,
184         defaults: defaults
185       };
186     }
187
188     return validate;
189   }
190
191   function resolveRef(baseId, ref, isRoot) {
192     ref = resolve.url(baseId, ref);
193     var refIndex = refs[ref];
194     var _refVal, refCode;
195     if (refIndex !== undefined) {
196       _refVal = refVal[refIndex];
197       refCode = 'refVal[' + refIndex + ']';
198       return resolvedRef(_refVal, refCode);
199     }
200     if (!isRoot && root.refs) {
201       var rootRefId = root.refs[ref];
202       if (rootRefId !== undefined) {
203         _refVal = root.refVal[rootRefId];
204         refCode = addLocalRef(ref, _refVal);
205         return resolvedRef(_refVal, refCode);
206       }
207     }
208
209     refCode = addLocalRef(ref);
210     var v = resolve.call(self, localCompile, root, ref);
211     if (!v) {
212       var localSchema = localRefs && localRefs[ref];
213       if (localSchema) {
214         v = resolve.inlineRef(localSchema, opts.inlineRefs)
215             ? localSchema
216             : compile.call(self, localSchema, root, localRefs, baseId);
217       }
218     }
219
220     if (v) {
221       replaceLocalRef(ref, v);
222       return resolvedRef(v, refCode);
223     }
224   }
225
226   function addLocalRef(ref, v) {
227     var refId = refVal.length;
228     refVal[refId] = v;
229     refs[ref] = refId;
230     return 'refVal' + refId;
231   }
232
233   function replaceLocalRef(ref, v) {
234     var refId = refs[ref];
235     refVal[refId] = v;
236   }
237
238   function resolvedRef(refVal, code) {
239     return typeof refVal == 'object'
240             ? { code: code, schema: refVal, inline: true }
241             : { code: code, $async: refVal && refVal.$async };
242   }
243
244   function usePattern(regexStr) {
245     var index = patternsHash[regexStr];
246     if (index === undefined) {
247       index = patternsHash[regexStr] = patterns.length;
248       patterns[index] = regexStr;
249     }
250     return 'pattern' + index;
251   }
252
253   function useDefault(value) {
254     switch (typeof value) {
255       case 'boolean':
256       case 'number':
257         return '' + value;
258       case 'string':
259         return util.toQuotedString(value);
260       case 'object':
261         if (value === null) return 'null';
262         var valueStr = stableStringify(value);
263         var index = defaultsHash[valueStr];
264         if (index === undefined) {
265           index = defaultsHash[valueStr] = defaults.length;
266           defaults[index] = value;
267         }
268         return 'default' + index;
269     }
270   }
271
272   function useCustomRule(rule, schema, parentSchema, it) {
273     var validateSchema = rule.definition.validateSchema;
274     if (validateSchema && self._opts.validateSchema !== false) {
275       var valid = validateSchema(schema);
276       if (!valid) {
277         var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
278         if (self._opts.validateSchema == 'log') console.error(message);
279         else throw new Error(message);
280       }
281     }
282
283     var compile = rule.definition.compile
284       , inline = rule.definition.inline
285       , macro = rule.definition.macro;
286
287     var validate;
288     if (compile) {
289       validate = compile.call(self, schema, parentSchema, it);
290     } else if (macro) {
291       validate = macro.call(self, schema, parentSchema, it);
292       if (opts.validateSchema !== false) self.validateSchema(validate, true);
293     } else if (inline) {
294       validate = inline.call(self, it, rule.keyword, schema, parentSchema);
295     } else {
296       validate = rule.definition.validate;
297     }
298
299     var index = customRules.length;
300     customRules[index] = validate;
301
302     return {
303       code: 'customRule' + index,
304       validate: validate
305     };
306   }
307 }
308
309
310 /**
311  * Checks if the schema is currently compiled
312  * @this   Ajv
313  * @param  {Object} schema schema to compile
314  * @param  {Object} root root object
315  * @param  {String} baseId base schema ID
316  * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean)
317  */
318 function checkCompiling(schema, root, baseId) {
319   /* jshint validthis: true */
320   var index = compIndex.call(this, schema, root, baseId);
321   if (index >= 0) return { index: index, compiling: true };
322   index = this._compilations.length;
323   this._compilations[index] = {
324     schema: schema,
325     root: root,
326     baseId: baseId
327   };
328   return { index: index, compiling: false };
329 }
330
331
332 /**
333  * Removes the schema from the currently compiled list
334  * @this   Ajv
335  * @param  {Object} schema schema to compile
336  * @param  {Object} root root object
337  * @param  {String} baseId base schema ID
338  */
339 function endCompiling(schema, root, baseId) {
340   /* jshint validthis: true */
341   var i = compIndex.call(this, schema, root, baseId);
342   if (i >= 0) this._compilations.splice(i, 1);
343 }
344
345
346 /**
347  * Index of schema compilation in the currently compiled list
348  * @this   Ajv
349  * @param  {Object} schema schema to compile
350  * @param  {Object} root root object
351  * @param  {String} baseId base schema ID
352  * @return {Integer} compilation index
353  */
354 function compIndex(schema, root, baseId) {
355   /* jshint validthis: true */
356   for (var i=0; i<this._compilations.length; i++) {
357     var c = this._compilations[i];
358     if (c.schema == schema && c.root == root && c.baseId == baseId) return i;
359   }
360   return -1;
361 }
362
363
364 function patternCode(i, patterns) {
365   return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');';
366 }
367
368
369 function defaultCode(i) {
370   return 'var default' + i + ' = defaults[' + i + '];';
371 }
372
373
374 function refValCode(i, refVal) {
375   return refVal[i] ? 'var refVal' + i + ' = refVal[' + i + '];' : '';
376 }
377
378
379 function customRuleCode(i) {
380   return 'var customRule' + i + ' = customRules[' + i + '];';
381 }
382
383
384 function vars(arr, statement) {
385   if (!arr.length) return '';
386   var code = '';
387   for (var i=0; i<arr.length; i++)
388     code += statement(i, arr);
389   return code;
390 }