1 import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
2 import Exception from '../exception';
3 import {isArray} from '../utils';
4 import CodeGen from './code-gen';
6 function Literal(value) {
10 function JavaScriptCompiler() {}
12 JavaScriptCompiler.prototype = {
13 // PUBLIC API: You can override these methods in a subclass to provide
14 // alternative compiled forms for name lookup and buffering semantics
15 nameLookup: function(parent, name/* , type*/) {
16 if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
17 return [parent, '.', name];
19 return [parent, '[', JSON.stringify(name), ']'];
22 depthedLookup: function(name) {
23 return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
26 compilerInfo: function() {
27 const revision = COMPILER_REVISION,
28 versions = REVISION_CHANGES[revision];
29 return [revision, versions];
32 appendToBuffer: function(source, location, explicit) {
33 // Force a source as this simplifies the merge logic.
34 if (!isArray(source)) {
37 source = this.source.wrap(source, location);
39 if (this.environment.isSimple) {
40 return ['return ', source, ';'];
41 } else if (explicit) {
42 // This is a case where the buffer operation occurs as a child of another
43 // construct, generally braces. We have to explicitly output these buffer
44 // operations to ensure that the emitted code goes in the correct location.
45 return ['buffer += ', source, ';'];
47 source.appendToBuffer = true;
52 initializeBuffer: function() {
53 return this.quotedString('');
57 compile: function(environment, options, context, asObject) {
58 this.environment = environment;
59 this.options = options;
60 this.stringParams = this.options.stringParams;
61 this.trackIds = this.options.trackIds;
62 this.precompile = !asObject;
64 this.name = this.environment.name;
65 this.isChild = !!context;
66 this.context = context || {
77 this.registers = { list: [] };
79 this.compileStack = [];
80 this.inlineStack = [];
81 this.blockParams = [];
83 this.compileChildren(environment, options);
85 this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
86 this.useBlockParams = this.useBlockParams || environment.useBlockParams;
88 let opcodes = environment.opcodes,
94 for (i = 0, l = opcodes.length; i < l; i++) {
97 this.source.currentLocation = opcode.loc;
98 firstLoc = firstLoc || opcode.loc;
99 this[opcode.opcode].apply(this, opcode.args);
102 // Flush any trailing content that might be pending.
103 this.source.currentLocation = firstLoc;
106 /* istanbul ignore next */
107 if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
108 throw new Exception('Compile completed with content left on stack');
111 if (!this.decorators.isEmpty()) {
112 this.useDecorators = true;
114 this.decorators.prepend('var decorators = container.decorators;\n');
115 this.decorators.push('return fn;');
118 this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
120 this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
121 this.decorators.push('}\n');
122 this.decorators = this.decorators.merge();
125 this.decorators = undefined;
128 let fn = this.createFunctionContext(asObject);
131 compiler: this.compilerInfo(),
135 if (this.decorators) {
136 ret.main_d = this.decorators; // eslint-disable-line camelcase
137 ret.useDecorators = true;
140 let {programs, decorators} = this.context;
141 for (i = 0, l = programs.length; i < l; i++) {
143 ret[i] = programs[i];
145 ret[i + '_d'] = decorators[i];
146 ret.useDecorators = true;
151 if (this.environment.usePartial) {
152 ret.usePartial = true;
154 if (this.options.data) {
157 if (this.useDepths) {
158 ret.useDepths = true;
160 if (this.useBlockParams) {
161 ret.useBlockParams = true;
163 if (this.options.compat) {
168 ret.compiler = JSON.stringify(ret.compiler);
170 this.source.currentLocation = {start: {line: 1, column: 0}};
171 ret = this.objectLiteral(ret);
173 if (options.srcName) {
174 ret = ret.toStringWithSourceMap({file: options.destName});
175 ret.map = ret.map && ret.map.toString();
177 ret = ret.toString();
180 ret.compilerOptions = this.options;
189 preamble: function() {
190 // track the last context pushed into place to allow skipping the
191 // getContext opcode when it would be a noop
192 this.lastContext = 0;
193 this.source = new CodeGen(this.options.srcName);
194 this.decorators = new CodeGen(this.options.srcName);
197 createFunctionContext: function(asObject) {
198 let varDeclarations = '';
200 let locals = this.stackVars.concat(this.registers.list);
201 if (locals.length > 0) {
202 varDeclarations += ', ' + locals.join(', ');
205 // Generate minimizer alias mappings
207 // When using true SourceNodes, this will update all references to the given alias
208 // as the source nodes are reused in situ. For the non-source node compilation mode,
209 // aliases will not be used, but this case is already being run on the client and
210 // we aren't concern about minimizing the template size.
212 for (let alias in this.aliases) { // eslint-disable-line guard-for-in
213 let node = this.aliases[alias];
215 if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
216 varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
217 node.children[0] = 'alias' + aliasCount;
221 let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
223 if (this.useBlockParams || this.useDepths) {
224 params.push('blockParams');
226 if (this.useDepths) {
227 params.push('depths');
230 // Perform a second pass over the output to merge content when possible
231 let source = this.mergeSource(varDeclarations);
236 return Function.apply(this, params);
238 return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
241 mergeSource: function(varDeclarations) {
242 let isSimple = this.environment.isSimple,
243 appendOnly = !this.forceBuffer,
249 this.source.each((line) => {
250 if (line.appendToBuffer) {
262 bufferStart.prepend('buffer += ');
265 bufferStart = bufferEnd = undefined;
278 bufferStart.prepend('return ');
280 } else if (!sourceSeen) {
281 this.source.push('return "";');
284 varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
287 bufferStart.prepend('return buffer + ');
290 this.source.push('return buffer;');
294 if (varDeclarations) {
295 this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
298 return this.source.merge();
303 // On stack, before: hash, inverse, program, value
304 // On stack, after: return value of blockHelperMissing
306 // The purpose of this opcode is to take a block of the form
307 // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
308 // replace it on the stack with the result of properly
309 // invoking blockHelperMissing.
310 blockValue: function(name) {
311 let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
312 params = [this.contextName(0)];
313 this.setupHelperArgs(name, 0, params);
315 let blockName = this.popStack();
316 params.splice(1, 0, blockName);
318 this.push(this.source.functionCall(blockHelperMissing, 'call', params));
321 // [ambiguousBlockValue]
323 // On stack, before: hash, inverse, program, value
324 // Compiler value, before: lastHelper=value of last found helper, if any
325 // On stack, after, if no lastHelper: same as [blockValue]
326 // On stack, after, if lastHelper: value
327 ambiguousBlockValue: function() {
328 // We're being a bit cheeky and reusing the options value from the prior exec
329 let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
330 params = [this.contextName(0)];
331 this.setupHelperArgs('', 0, params, true);
335 let current = this.topStack();
336 params.splice(1, 0, current);
339 'if (!', this.lastHelper, ') { ',
340 current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
346 // On stack, before: ...
347 // On stack, after: ...
349 // Appends the string value of `content` to the current buffer
350 appendContent: function(content) {
351 if (this.pendingContent) {
352 content = this.pendingContent + content;
354 this.pendingLocation = this.source.currentLocation;
357 this.pendingContent = content;
362 // On stack, before: value, ...
363 // On stack, after: ...
365 // Coerces `value` to a String and appends it to the current buffer.
367 // If `value` is truthy, or 0, it is coerced into a string and appended
368 // Otherwise, the empty string is appended
370 if (this.isInline()) {
371 this.replaceStack((current) => [' != null ? ', current, ' : ""']);
373 this.pushSource(this.appendToBuffer(this.popStack()));
375 let local = this.popStack();
376 this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
377 if (this.environment.isSimple) {
378 this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
385 // On stack, before: value, ...
386 // On stack, after: ...
388 // Escape `value` and append it to the buffer
389 appendEscaped: function() {
390 this.pushSource(this.appendToBuffer(
391 [this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
396 // On stack, before: ...
397 // On stack, after: ...
398 // Compiler value, after: lastContext=depth
400 // Set the value of the `lastContext` compiler value to the depth
401 getContext: function(depth) {
402 this.lastContext = depth;
407 // On stack, before: ...
408 // On stack, after: currentContext, ...
410 // Pushes the value of the current context onto the stack.
411 pushContext: function() {
412 this.pushStackLiteral(this.contextName(this.lastContext));
417 // On stack, before: ...
418 // On stack, after: currentContext[name], ...
420 // Looks up the value of `name` on the current context and pushes
421 // it onto the stack.
422 lookupOnContext: function(parts, falsy, strict, scoped) {
425 if (!scoped && this.options.compat && !this.lastContext) {
426 // The depthed query is expected to handle the undefined logic for the root level that
427 // is implemented below, so we evaluate that directly in compat mode
428 this.push(this.depthedLookup(parts[i++]));
433 this.resolvePath('context', parts, i, falsy, strict);
436 // [lookupBlockParam]
438 // On stack, before: ...
439 // On stack, after: blockParam[name], ...
441 // Looks up the value of `parts` on the given block param and pushes
442 // it onto the stack.
443 lookupBlockParam: function(blockParamId, parts) {
444 this.useBlockParams = true;
446 this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
447 this.resolvePath('context', parts, 1);
452 // On stack, before: ...
453 // On stack, after: data, ...
455 // Push the data lookup operator
456 lookupData: function(depth, parts, strict) {
458 this.pushStackLiteral('data');
460 this.pushStackLiteral('container.data(data, ' + depth + ')');
463 this.resolvePath('data', parts, 0, true, strict);
466 resolvePath: function(type, parts, i, falsy, strict) {
467 if (this.options.strict || this.options.assumeObjects) {
468 this.push(strictLookup(this.options.strict && strict, this, parts, type));
472 let len = parts.length;
473 for (; i < len; i++) {
474 /* eslint-disable no-loop-func */
475 this.replaceStack((current) => {
476 let lookup = this.nameLookup(current, parts[i], type);
477 // We want to ensure that zero and false are handled properly if the context (falsy flag)
478 // needs to have the special handling for these values.
480 return [' != null ? ', lookup, ' : ', current];
482 // Otherwise we can use generic falsy handling
483 return [' && ', lookup];
486 /* eslint-enable no-loop-func */
490 // [resolvePossibleLambda]
492 // On stack, before: value, ...
493 // On stack, after: resolved value, ...
495 // If the `value` is a lambda, replace it on the stack by
496 // the return value of the lambda
497 resolvePossibleLambda: function() {
498 this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
503 // On stack, before: ...
504 // On stack, after: string, currentContext, ...
506 // This opcode is designed for use in string mode, which
507 // provides the string value of a parameter along with its
508 // depth rather than resolving it immediately.
509 pushStringParam: function(string, type) {
511 this.pushString(type);
513 // If it's a subexpression, the string result
514 // will be pushed after this opcode.
515 if (type !== 'SubExpression') {
516 if (typeof string === 'string') {
517 this.pushString(string);
519 this.pushStackLiteral(string);
524 emptyHash: function(omitEmpty) {
526 this.push('{}'); // hashIds
528 if (this.stringParams) {
529 this.push('{}'); // hashContexts
530 this.push('{}'); // hashTypes
532 this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
534 pushHash: function() {
536 this.hashes.push(this.hash);
538 this.hash = {values: [], types: [], contexts: [], ids: []};
540 popHash: function() {
541 let hash = this.hash;
542 this.hash = this.hashes.pop();
545 this.push(this.objectLiteral(hash.ids));
547 if (this.stringParams) {
548 this.push(this.objectLiteral(hash.contexts));
549 this.push(this.objectLiteral(hash.types));
552 this.push(this.objectLiteral(hash.values));
557 // On stack, before: ...
558 // On stack, after: quotedString(string), ...
560 // Push a quoted version of `string` onto the stack
561 pushString: function(string) {
562 this.pushStackLiteral(this.quotedString(string));
567 // On stack, before: ...
568 // On stack, after: value, ...
570 // Pushes a value onto the stack. This operation prevents
571 // the compiler from creating a temporary variable to hold
573 pushLiteral: function(value) {
574 this.pushStackLiteral(value);
579 // On stack, before: ...
580 // On stack, after: program(guid), ...
582 // Push a program expression onto the stack. This takes
583 // a compile-time guid and converts it into a runtime-accessible
585 pushProgram: function(guid) {
587 this.pushStackLiteral(this.programExpression(guid));
589 this.pushStackLiteral(null);
593 // [registerDecorator]
595 // On stack, before: hash, program, params..., ...
596 // On stack, after: ...
598 // Pops off the decorator's parameters, invokes the decorator,
599 // and inserts the decorator into the decorators list.
600 registerDecorator(paramSize, name) {
601 let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
602 options = this.setupHelperArgs(name, paramSize);
604 this.decorators.push([
606 this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
613 // On stack, before: hash, inverse, program, params..., ...
614 // On stack, after: result of helper invocation
616 // Pops off the helper's parameters, invokes the helper,
617 // and pushes the helper's return value onto the stack.
619 // If the helper is not found, `helperMissing` is called.
620 invokeHelper: function(paramSize, name, isSimple) {
621 let nonHelper = this.popStack(),
622 helper = this.setupHelper(paramSize, name),
623 simple = isSimple ? [helper.name, ' || '] : '';
625 let lookup = ['('].concat(simple, nonHelper);
626 if (!this.options.strict) {
627 lookup.push(' || ', this.aliasable('helpers.helperMissing'));
631 this.push(this.source.functionCall(lookup, 'call', helper.callParams));
634 // [invokeKnownHelper]
636 // On stack, before: hash, inverse, program, params..., ...
637 // On stack, after: result of helper invocation
639 // This operation is used when the helper is known to exist,
640 // so a `helperMissing` fallback is not required.
641 invokeKnownHelper: function(paramSize, name) {
642 let helper = this.setupHelper(paramSize, name);
643 this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
648 // On stack, before: hash, inverse, program, params..., ...
649 // On stack, after: result of disambiguation
651 // This operation is used when an expression like `{{foo}}`
652 // is provided, but we don't know at compile-time whether it
653 // is a helper or a path.
655 // This operation emits more code than the other options,
656 // and can be avoided by passing the `knownHelpers` and
657 // `knownHelpersOnly` flags at compile-time.
658 invokeAmbiguous: function(name, helperCall) {
659 this.useRegister('helper');
661 let nonHelper = this.popStack();
664 let helper = this.setupHelper(0, name, helperCall);
666 let helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
668 let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
669 if (!this.options.strict) {
670 lookup[0] = '(helper = ';
672 ' != null ? helper : ',
673 this.aliasable('helpers.helperMissing')
679 (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
680 '(typeof helper === ', this.aliasable('"function"'), ' ? ',
681 this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
687 // On stack, before: context, ...
688 // On stack after: result of partial invocation
690 // This operation pops off a context, invokes a partial with that context,
691 // and pushes the result of the invocation back.
692 invokePartial: function(isDynamic, name, indent) {
694 options = this.setupParams(name, 1, params);
697 name = this.popStack();
702 options.indent = JSON.stringify(indent);
704 options.helpers = 'helpers';
705 options.partials = 'partials';
706 options.decorators = 'container.decorators';
709 params.unshift(this.nameLookup('partials', name, 'partial'));
711 params.unshift(name);
714 if (this.options.compat) {
715 options.depths = 'depths';
717 options = this.objectLiteral(options);
718 params.push(options);
720 this.push(this.source.functionCall('container.invokePartial', '', params));
725 // On stack, before: value, ..., hash, ...
726 // On stack, after: ..., hash, ...
728 // Pops a value off the stack and assigns it to the current hash
729 assignToHash: function(key) {
730 let value = this.popStack(),
736 id = this.popStack();
738 if (this.stringParams) {
739 type = this.popStack();
740 context = this.popStack();
743 let hash = this.hash;
745 hash.contexts[key] = context;
748 hash.types[key] = type;
753 hash.values[key] = value;
756 pushId: function(type, name, child) {
757 if (type === 'BlockParam') {
758 this.pushStackLiteral(
759 'blockParams[' + name[0] + '].path[' + name[1] + ']'
760 + (child ? ' + ' + JSON.stringify('.' + child) : ''));
761 } else if (type === 'PathExpression') {
762 this.pushString(name);
763 } else if (type === 'SubExpression') {
764 this.pushStackLiteral('true');
766 this.pushStackLiteral('null');
772 compiler: JavaScriptCompiler,
774 compileChildren: function(environment, options) {
775 let children = environment.children, child, compiler;
777 for (let i = 0, l = children.length; i < l; i++) {
779 compiler = new this.compiler(); // eslint-disable-line new-cap
781 let existing = this.matchExistingProgram(child);
783 if (existing == null) {
784 this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
785 let index = this.context.programs.length;
787 child.name = 'program' + index;
788 this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
789 this.context.decorators[index] = compiler.decorators;
790 this.context.environments[index] = child;
792 this.useDepths = this.useDepths || compiler.useDepths;
793 this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
794 child.useDepths = this.useDepths;
795 child.useBlockParams = this.useBlockParams;
797 child.index = existing.index;
798 child.name = 'program' + existing.index;
800 this.useDepths = this.useDepths || existing.useDepths;
801 this.useBlockParams = this.useBlockParams || existing.useBlockParams;
805 matchExistingProgram: function(child) {
806 for (let i = 0, len = this.context.environments.length; i < len; i++) {
807 let environment = this.context.environments[i];
808 if (environment && environment.equals(child)) {
814 programExpression: function(guid) {
815 let child = this.environment.children[guid],
816 programParams = [child.index, 'data', child.blockParams];
818 if (this.useBlockParams || this.useDepths) {
819 programParams.push('blockParams');
821 if (this.useDepths) {
822 programParams.push('depths');
825 return 'container.program(' + programParams.join(', ') + ')';
828 useRegister: function(name) {
829 if (!this.registers[name]) {
830 this.registers[name] = true;
831 this.registers.list.push(name);
835 push: function(expr) {
836 if (!(expr instanceof Literal)) {
837 expr = this.source.wrap(expr);
840 this.inlineStack.push(expr);
844 pushStackLiteral: function(item) {
845 this.push(new Literal(item));
848 pushSource: function(source) {
849 if (this.pendingContent) {
851 this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
852 this.pendingContent = undefined;
856 this.source.push(source);
860 replaceStack: function(callback) {
866 /* istanbul ignore next */
867 if (!this.isInline()) {
868 throw new Exception('replaceStack on non-inline');
871 // We want to merge the inline statement into the replacement statement via ','
872 let top = this.popStack(true);
874 if (top instanceof Literal) {
875 // Literals do not need to be inlined
877 prefix = ['(', stack];
880 // Get or create the current stack name for use by the inline
882 let name = this.incrStack();
884 prefix = ['((', this.push(name), ' = ', top, ')'];
885 stack = this.topStack();
888 let item = callback.call(this, stack);
896 this.push(prefix.concat(item, ')'));
899 incrStack: function() {
901 if (this.stackSlot > this.stackVars.length) { this.stackVars.push('stack' + this.stackSlot); }
902 return this.topStackName();
904 topStackName: function() {
905 return 'stack' + this.stackSlot;
907 flushInline: function() {
908 let inlineStack = this.inlineStack;
909 this.inlineStack = [];
910 for (let i = 0, len = inlineStack.length; i < len; i++) {
911 let entry = inlineStack[i];
912 /* istanbul ignore if */
913 if (entry instanceof Literal) {
914 this.compileStack.push(entry);
916 let stack = this.incrStack();
917 this.pushSource([stack, ' = ', entry, ';']);
918 this.compileStack.push(stack);
922 isInline: function() {
923 return this.inlineStack.length;
926 popStack: function(wrapped) {
927 let inline = this.isInline(),
928 item = (inline ? this.inlineStack : this.compileStack).pop();
930 if (!wrapped && (item instanceof Literal)) {
934 /* istanbul ignore next */
935 if (!this.stackSlot) {
936 throw new Exception('Invalid stack pop');
944 topStack: function() {
945 let stack = (this.isInline() ? this.inlineStack : this.compileStack),
946 item = stack[stack.length - 1];
948 /* istanbul ignore if */
949 if (item instanceof Literal) {
956 contextName: function(context) {
957 if (this.useDepths && context) {
958 return 'depths[' + context + ']';
960 return 'depth' + context;
964 quotedString: function(str) {
965 return this.source.quotedString(str);
968 objectLiteral: function(obj) {
969 return this.source.objectLiteral(obj);
972 aliasable: function(name) {
973 let ret = this.aliases[name];
975 ret.referenceCount++;
979 ret = this.aliases[name] = this.source.wrap(name);
980 ret.aliasable = true;
981 ret.referenceCount = 1;
986 setupHelper: function(paramSize, name, blockHelper) {
988 paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
989 let foundHelper = this.nameLookup('helpers', name, 'helper'),
990 callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : {}`);
994 paramsInit: paramsInit,
996 callParams: [callContext].concat(params)
1000 setupParams: function(helper, paramSize, params) {
1005 objectArgs = !params,
1012 options.name = this.quotedString(helper);
1013 options.hash = this.popStack();
1015 if (this.trackIds) {
1016 options.hashIds = this.popStack();
1018 if (this.stringParams) {
1019 options.hashTypes = this.popStack();
1020 options.hashContexts = this.popStack();
1023 let inverse = this.popStack(),
1024 program = this.popStack();
1026 // Avoid setting fn and inverse if neither are set. This allows
1027 // helpers to do a check for `if (options.fn)`
1028 if (program || inverse) {
1029 options.fn = program || 'container.noop';
1030 options.inverse = inverse || 'container.noop';
1033 // The parameters go on to the stack in order (making sure that they are evaluated in order)
1034 // so we need to pop them off the stack in reverse order
1037 param = this.popStack();
1040 if (this.trackIds) {
1041 ids[i] = this.popStack();
1043 if (this.stringParams) {
1044 types[i] = this.popStack();
1045 contexts[i] = this.popStack();
1050 options.args = this.source.generateArray(params);
1053 if (this.trackIds) {
1054 options.ids = this.source.generateArray(ids);
1056 if (this.stringParams) {
1057 options.types = this.source.generateArray(types);
1058 options.contexts = this.source.generateArray(contexts);
1061 if (this.options.data) {
1062 options.data = 'data';
1064 if (this.useBlockParams) {
1065 options.blockParams = 'blockParams';
1070 setupHelperArgs: function(helper, paramSize, params, useRegister) {
1071 let options = this.setupParams(helper, paramSize, params);
1072 options = this.objectLiteral(options);
1074 this.useRegister('options');
1075 params.push('options');
1076 return ['options=', options];
1077 } else if (params) {
1078 params.push(options);
1088 const reservedWords = (
1089 'break else new var' +
1090 ' case finally return void' +
1091 ' catch for switch while' +
1092 ' continue function this with' +
1093 ' default if throw' +
1095 ' do instanceof typeof' +
1096 ' abstract enum int short' +
1097 ' boolean export interface static' +
1098 ' byte extends long super' +
1099 ' char final native synchronized' +
1100 ' class float package throws' +
1101 ' const goto private transient' +
1102 ' debugger implements protected volatile' +
1103 ' double import public let yield await' +
1107 const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1109 for (let i = 0, l = reservedWords.length; i < l; i++) {
1110 compilerWords[reservedWords[i]] = true;
1114 JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
1115 return !JavaScriptCompiler.RESERVED_WORDS[name] && (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/).test(name);
1118 function strictLookup(requireTerminal, compiler, parts, type) {
1119 let stack = compiler.popStack(),
1122 if (requireTerminal) {
1126 for (; i < len; i++) {
1127 stack = compiler.nameLookup(stack, parts[i], type);
1130 if (requireTerminal) {
1131 return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
1137 export default JavaScriptCompiler;