Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / handlebars / lib / handlebars / compiler / compiler.js
1 /* eslint-disable new-cap */
2
3 import Exception from '../exception';
4 import {isArray, indexOf} from '../utils';
5 import AST from './ast';
6
7 const slice = [].slice;
8
9 export function Compiler() {}
10
11 // the foundHelper register will disambiguate helper lookup from finding a
12 // function in a context. This is necessary for mustache compatibility, which
13 // requires that context functions in blocks are evaluated by blockHelperMissing,
14 // and then proceed as if the resulting value was provided to blockHelperMissing.
15
16 Compiler.prototype = {
17   compiler: Compiler,
18
19   equals: function(other) {
20     let len = this.opcodes.length;
21     if (other.opcodes.length !== len) {
22       return false;
23     }
24
25     for (let i = 0; i < len; i++) {
26       let opcode = this.opcodes[i],
27           otherOpcode = other.opcodes[i];
28       if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
29         return false;
30       }
31     }
32
33     // We know that length is the same between the two arrays because they are directly tied
34     // to the opcode behavior above.
35     len = this.children.length;
36     for (let i = 0; i < len; i++) {
37       if (!this.children[i].equals(other.children[i])) {
38         return false;
39       }
40     }
41
42     return true;
43   },
44
45   guid: 0,
46
47   compile: function(program, options) {
48     this.sourceNode = [];
49     this.opcodes = [];
50     this.children = [];
51     this.options = options;
52     this.stringParams = options.stringParams;
53     this.trackIds = options.trackIds;
54
55     options.blockParams = options.blockParams || [];
56
57     // These changes will propagate to the other compiler components
58     let knownHelpers = options.knownHelpers;
59     options.knownHelpers = {
60       'helperMissing': true,
61       'blockHelperMissing': true,
62       'each': true,
63       'if': true,
64       'unless': true,
65       'with': true,
66       'log': true,
67       'lookup': true
68     };
69     if (knownHelpers) {
70       for (let name in knownHelpers) {
71         /* istanbul ignore else */
72         if (name in knownHelpers) {
73           options.knownHelpers[name] = knownHelpers[name];
74         }
75       }
76     }
77
78     return this.accept(program);
79   },
80
81   compileProgram: function(program) {
82     let childCompiler = new this.compiler(), // eslint-disable-line new-cap
83         result = childCompiler.compile(program, this.options),
84         guid = this.guid++;
85
86     this.usePartial = this.usePartial || result.usePartial;
87
88     this.children[guid] = result;
89     this.useDepths = this.useDepths || result.useDepths;
90
91     return guid;
92   },
93
94   accept: function(node) {
95     /* istanbul ignore next: Sanity code */
96     if (!this[node.type]) {
97       throw new Exception('Unknown type: ' + node.type, node);
98     }
99
100     this.sourceNode.unshift(node);
101     let ret = this[node.type](node);
102     this.sourceNode.shift();
103     return ret;
104   },
105
106   Program: function(program) {
107     this.options.blockParams.unshift(program.blockParams);
108
109     let body = program.body,
110         bodyLength = body.length;
111     for (let i = 0; i < bodyLength; i++) {
112       this.accept(body[i]);
113     }
114
115     this.options.blockParams.shift();
116
117     this.isSimple = bodyLength === 1;
118     this.blockParams = program.blockParams ? program.blockParams.length : 0;
119
120     return this;
121   },
122
123   BlockStatement: function(block) {
124     transformLiteralToPath(block);
125
126     let program = block.program,
127         inverse = block.inverse;
128
129     program = program && this.compileProgram(program);
130     inverse = inverse && this.compileProgram(inverse);
131
132     let type = this.classifySexpr(block);
133
134     if (type === 'helper') {
135       this.helperSexpr(block, program, inverse);
136     } else if (type === 'simple') {
137       this.simpleSexpr(block);
138
139       // now that the simple mustache is resolved, we need to
140       // evaluate it by executing `blockHelperMissing`
141       this.opcode('pushProgram', program);
142       this.opcode('pushProgram', inverse);
143       this.opcode('emptyHash');
144       this.opcode('blockValue', block.path.original);
145     } else {
146       this.ambiguousSexpr(block, program, inverse);
147
148       // now that the simple mustache is resolved, we need to
149       // evaluate it by executing `blockHelperMissing`
150       this.opcode('pushProgram', program);
151       this.opcode('pushProgram', inverse);
152       this.opcode('emptyHash');
153       this.opcode('ambiguousBlockValue');
154     }
155
156     this.opcode('append');
157   },
158
159   DecoratorBlock(decorator) {
160     let program = decorator.program && this.compileProgram(decorator.program);
161     let params = this.setupFullMustacheParams(decorator, program, undefined),
162         path = decorator.path;
163
164     this.useDecorators = true;
165     this.opcode('registerDecorator', params.length, path.original);
166   },
167
168   PartialStatement: function(partial) {
169     this.usePartial = true;
170
171     let program = partial.program;
172     if (program) {
173       program = this.compileProgram(partial.program);
174     }
175
176     let params = partial.params;
177     if (params.length > 1) {
178       throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
179     } else if (!params.length) {
180       if (this.options.explicitPartialContext) {
181         this.opcode('pushLiteral', 'undefined');
182       } else {
183         params.push({type: 'PathExpression', parts: [], depth: 0});
184       }
185     }
186
187     let partialName = partial.name.original,
188         isDynamic = partial.name.type === 'SubExpression';
189     if (isDynamic) {
190       this.accept(partial.name);
191     }
192
193     this.setupFullMustacheParams(partial, program, undefined, true);
194
195     let indent = partial.indent || '';
196     if (this.options.preventIndent && indent) {
197       this.opcode('appendContent', indent);
198       indent = '';
199     }
200
201     this.opcode('invokePartial', isDynamic, partialName, indent);
202     this.opcode('append');
203   },
204   PartialBlockStatement: function(partialBlock) {
205     this.PartialStatement(partialBlock);
206   },
207
208   MustacheStatement: function(mustache) {
209     this.SubExpression(mustache);
210
211     if (mustache.escaped && !this.options.noEscape) {
212       this.opcode('appendEscaped');
213     } else {
214       this.opcode('append');
215     }
216   },
217   Decorator(decorator) {
218     this.DecoratorBlock(decorator);
219   },
220
221
222   ContentStatement: function(content) {
223     if (content.value) {
224       this.opcode('appendContent', content.value);
225     }
226   },
227
228   CommentStatement: function() {},
229
230   SubExpression: function(sexpr) {
231     transformLiteralToPath(sexpr);
232     let type = this.classifySexpr(sexpr);
233
234     if (type === 'simple') {
235       this.simpleSexpr(sexpr);
236     } else if (type === 'helper') {
237       this.helperSexpr(sexpr);
238     } else {
239       this.ambiguousSexpr(sexpr);
240     }
241   },
242   ambiguousSexpr: function(sexpr, program, inverse) {
243     let path = sexpr.path,
244         name = path.parts[0],
245         isBlock = program != null || inverse != null;
246
247     this.opcode('getContext', path.depth);
248
249     this.opcode('pushProgram', program);
250     this.opcode('pushProgram', inverse);
251
252     path.strict = true;
253     this.accept(path);
254
255     this.opcode('invokeAmbiguous', name, isBlock);
256   },
257
258   simpleSexpr: function(sexpr) {
259     let path = sexpr.path;
260     path.strict = true;
261     this.accept(path);
262     this.opcode('resolvePossibleLambda');
263   },
264
265   helperSexpr: function(sexpr, program, inverse) {
266     let params = this.setupFullMustacheParams(sexpr, program, inverse),
267         path = sexpr.path,
268         name = path.parts[0];
269
270     if (this.options.knownHelpers[name]) {
271       this.opcode('invokeKnownHelper', params.length, name);
272     } else if (this.options.knownHelpersOnly) {
273       throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
274     } else {
275       path.strict = true;
276       path.falsy = true;
277
278       this.accept(path);
279       this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
280     }
281   },
282
283   PathExpression: function(path) {
284     this.addDepth(path.depth);
285     this.opcode('getContext', path.depth);
286
287     let name = path.parts[0],
288         scoped = AST.helpers.scopedId(path),
289         blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
290
291     if (blockParamId) {
292       this.opcode('lookupBlockParam', blockParamId, path.parts);
293     } else if (!name) {
294       // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
295       this.opcode('pushContext');
296     } else if (path.data) {
297       this.options.data = true;
298       this.opcode('lookupData', path.depth, path.parts, path.strict);
299     } else {
300       this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
301     }
302   },
303
304   StringLiteral: function(string) {
305     this.opcode('pushString', string.value);
306   },
307
308   NumberLiteral: function(number) {
309     this.opcode('pushLiteral', number.value);
310   },
311
312   BooleanLiteral: function(bool) {
313     this.opcode('pushLiteral', bool.value);
314   },
315
316   UndefinedLiteral: function() {
317     this.opcode('pushLiteral', 'undefined');
318   },
319
320   NullLiteral: function() {
321     this.opcode('pushLiteral', 'null');
322   },
323
324   Hash: function(hash) {
325     let pairs = hash.pairs,
326         i = 0,
327         l = pairs.length;
328
329     this.opcode('pushHash');
330
331     for (; i < l; i++) {
332       this.pushParam(pairs[i].value);
333     }
334     while (i--) {
335       this.opcode('assignToHash', pairs[i].key);
336     }
337     this.opcode('popHash');
338   },
339
340   // HELPERS
341   opcode: function(name) {
342     this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
343   },
344
345   addDepth: function(depth) {
346     if (!depth) {
347       return;
348     }
349
350     this.useDepths = true;
351   },
352
353   classifySexpr: function(sexpr) {
354     let isSimple = AST.helpers.simpleId(sexpr.path);
355
356     let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
357
358     // a mustache is an eligible helper if:
359     // * its id is simple (a single part, not `this` or `..`)
360     let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
361
362     // if a mustache is an eligible helper but not a definite
363     // helper, it is ambiguous, and will be resolved in a later
364     // pass or at runtime.
365     let isEligible = !isBlockParam && (isHelper || isSimple);
366
367     // if ambiguous, we can possibly resolve the ambiguity now
368     // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
369     if (isEligible && !isHelper) {
370       let name = sexpr.path.parts[0],
371           options = this.options;
372
373       if (options.knownHelpers[name]) {
374         isHelper = true;
375       } else if (options.knownHelpersOnly) {
376         isEligible = false;
377       }
378     }
379
380     if (isHelper) {
381       return 'helper';
382     } else if (isEligible) {
383       return 'ambiguous';
384     } else {
385       return 'simple';
386     }
387   },
388
389   pushParams: function(params) {
390     for (let i = 0, l = params.length; i < l; i++) {
391       this.pushParam(params[i]);
392     }
393   },
394
395   pushParam: function(val) {
396     let value = val.value != null ? val.value : val.original || '';
397
398     if (this.stringParams) {
399       if (value.replace) {
400         value = value
401             .replace(/^(\.?\.\/)*/g, '')
402             .replace(/\//g, '.');
403       }
404
405       if (val.depth) {
406         this.addDepth(val.depth);
407       }
408       this.opcode('getContext', val.depth || 0);
409       this.opcode('pushStringParam', value, val.type);
410
411       if (val.type === 'SubExpression') {
412         // SubExpressions get evaluated and passed in
413         // in string params mode.
414         this.accept(val);
415       }
416     } else {
417       if (this.trackIds) {
418         let blockParamIndex;
419         if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
420            blockParamIndex = this.blockParamIndex(val.parts[0]);
421         }
422         if (blockParamIndex) {
423           let blockParamChild = val.parts.slice(1).join('.');
424           this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
425         } else {
426           value = val.original || value;
427           if (value.replace) {
428             value = value
429                 .replace(/^this(?:\.|$)/, '')
430                 .replace(/^\.\//, '')
431                 .replace(/^\.$/, '');
432           }
433
434           this.opcode('pushId', val.type, value);
435         }
436       }
437       this.accept(val);
438     }
439   },
440
441   setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
442     let params = sexpr.params;
443     this.pushParams(params);
444
445     this.opcode('pushProgram', program);
446     this.opcode('pushProgram', inverse);
447
448     if (sexpr.hash) {
449       this.accept(sexpr.hash);
450     } else {
451       this.opcode('emptyHash', omitEmpty);
452     }
453
454     return params;
455   },
456
457   blockParamIndex: function(name) {
458     for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
459       let blockParams = this.options.blockParams[depth],
460           param = blockParams && indexOf(blockParams, name);
461       if (blockParams && param >= 0) {
462         return [depth, param];
463       }
464     }
465   }
466 };
467
468 export function precompile(input, options, env) {
469   if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
470     throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
471   }
472
473   options = options || {};
474   if (!('data' in options)) {
475     options.data = true;
476   }
477   if (options.compat) {
478     options.useDepths = true;
479   }
480
481   let ast = env.parse(input, options),
482       environment = new env.Compiler().compile(ast, options);
483   return new env.JavaScriptCompiler().compile(environment, options);
484 }
485
486 export function compile(input, options = {}, env) {
487   if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
488     throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
489   }
490
491   if (!('data' in options)) {
492     options.data = true;
493   }
494   if (options.compat) {
495     options.useDepths = true;
496   }
497
498   let compiled;
499
500   function compileInput() {
501     let ast = env.parse(input, options),
502         environment = new env.Compiler().compile(ast, options),
503         templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
504     return env.template(templateSpec);
505   }
506
507   // Template is only compiled on first use and cached after that point.
508   function ret(context, execOptions) {
509     if (!compiled) {
510       compiled = compileInput();
511     }
512     return compiled.call(this, context, execOptions);
513   }
514   ret._setup = function(setupOptions) {
515     if (!compiled) {
516       compiled = compileInput();
517     }
518     return compiled._setup(setupOptions);
519   };
520   ret._child = function(i, data, blockParams, depths) {
521     if (!compiled) {
522       compiled = compileInput();
523     }
524     return compiled._child(i, data, blockParams, depths);
525   };
526   return ret;
527 }
528
529 function argEquals(a, b) {
530   if (a === b) {
531     return true;
532   }
533
534   if (isArray(a) && isArray(b) && a.length === b.length) {
535     for (let i = 0; i < a.length; i++) {
536       if (!argEquals(a[i], b[i])) {
537         return false;
538       }
539     }
540     return true;
541   }
542 }
543
544 function transformLiteralToPath(sexpr) {
545   if (!sexpr.path.parts) {
546     let literal = sexpr.path;
547     // Casting to string here to make false and 0 literal values play nicely with the rest
548     // of the system.
549     sexpr.path = {
550       type: 'PathExpression',
551       data: false,
552       depth: 0,
553       parts: [literal.original + ''],
554       original: literal.original + '',
555       loc: literal.loc
556     };
557   }
558 }