import * as Utils from './utils'; import Exception from './exception'; import { COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES } from './base'; import { moveHelperToHooks } from './helpers'; import { wrapHelper } from './internal/wrapHelper'; import { createProtoAccessControl, resultIsAllowed } from './internal/proto-access'; export function checkRevision(compilerInfo) { const compilerRevision = (compilerInfo && compilerInfo[0]) || 1, currentRevision = COMPILER_REVISION; if ( compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= COMPILER_REVISION ) { return; } if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) { const runtimeVersions = REVISION_CHANGES[currentRevision], compilerVersions = REVISION_CHANGES[compilerRevision]; throw new Exception( 'Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').' ); } else { // Use the embedded version info since the runtime doesn't know about this revision yet throw new Exception( 'Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').' ); } } export function template(templateSpec, env) { /* istanbul ignore next */ if (!env) { throw new Exception('No environment passed to template'); } if (!templateSpec || !templateSpec.main) { throw new Exception('Unknown template object: ' + typeof templateSpec); } templateSpec.main.decorator = templateSpec.main_d; // Note: Using env.VM references rather than local var references throughout this section to allow // for external users to override these as pseudo-supported APIs. env.VM.checkRevision(templateSpec.compiler); // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) const templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7; function invokePartialWrapper(partial, context, options) { if (options.hash) { context = Utils.extend({}, context, options.hash); if (options.ids) { options.ids[0] = true; } } partial = env.VM.resolvePartial.call(this, partial, context, options); let extendedOptions = Utils.extend({}, options, { hooks: this.hooks, protoAccessControl: this.protoAccessControl }); let result = env.VM.invokePartial.call( this, partial, context, extendedOptions ); if (result == null && env.compile) { options.partials[options.name] = env.compile( partial, templateSpec.compilerOptions, env ); result = options.partials[options.name](context, extendedOptions); } if (result != null) { if (options.indent) { let lines = result.split('\n'); for (let i = 0, l = lines.length; i < l; i++) { if (!lines[i] && i + 1 === l) { break; } lines[i] = options.indent + lines[i]; } result = lines.join('\n'); } return result; } else { throw new Exception( 'The partial ' + options.name + ' could not be compiled when running in runtime-only mode' ); } } // Just add water let container = { strict: function(obj, name, loc) { if (!obj || !(name in obj)) { throw new Exception('"' + name + '" not defined in ' + obj, { loc: loc }); } return container.lookupProperty(obj, name); }, lookupProperty: function(parent, propertyName) { let result = parent[propertyName]; if (result == null) { return result; } if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { return result; } if (resultIsAllowed(result, container.protoAccessControl, propertyName)) { return result; } return undefined; }, lookup: function(depths, name) { const len = depths.length; for (let i = 0; i < len; i++) { let result = depths[i] && container.lookupProperty(depths[i], name); if (result != null) { return depths[i][name]; } } }, lambda: function(current, context) { return typeof current === 'function' ? current.call(context) : current; }, escapeExpression: Utils.escapeExpression, invokePartial: invokePartialWrapper, fn: function(i) { let ret = templateSpec[i]; ret.decorator = templateSpec[i + '_d']; return ret; }, programs: [], program: function(i, data, declaredBlockParams, blockParams, depths) { let programWrapper = this.programs[i], fn = this.fn(i); if (data || depths || blockParams || declaredBlockParams) { programWrapper = wrapProgram( this, i, fn, data, declaredBlockParams, blockParams, depths ); } else if (!programWrapper) { programWrapper = this.programs[i] = wrapProgram(this, i, fn); } return programWrapper; }, data: function(value, depth) { while (value && depth--) { value = value._parent; } return value; }, mergeIfNeeded: function(param, common) { let obj = param || common; if (param && common && param !== common) { obj = Utils.extend({}, common, param); } return obj; }, // An empty object to use as replacement for null-contexts nullContext: Object.seal({}), noop: env.VM.noop, compilerInfo: templateSpec.compiler }; function ret(context, options = {}) { let data = options.data; ret._setup(options); if (!options.partial && templateSpec.useData) { data = initData(context, data); } let depths, blockParams = templateSpec.useBlockParams ? [] : undefined; if (templateSpec.useDepths) { if (options.depths) { depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; } else { depths = [context]; } } function main(context /*, options*/) { return ( '' + templateSpec.main( container, context, container.helpers, container.partials, data, blockParams, depths ) ); } main = executeDecorators( templateSpec.main, main, container, options.depths || [], data, blockParams ); return main(context, options); } ret.isTop = true; ret._setup = function(options) { if (!options.partial) { let mergedHelpers = Utils.extend({}, env.helpers, options.helpers); wrapHelpersToPassLookupProperty(mergedHelpers, container); container.helpers = mergedHelpers; if (templateSpec.usePartial) { // Use mergeIfNeeded here to prevent compiling global partials multiple times container.partials = container.mergeIfNeeded( options.partials, env.partials ); } if (templateSpec.usePartial || templateSpec.useDecorators) { container.decorators = Utils.extend( {}, env.decorators, options.decorators ); } container.hooks = {}; container.protoAccessControl = createProtoAccessControl(options); let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7; moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); } else { container.protoAccessControl = options.protoAccessControl; // internal option container.helpers = options.helpers; container.partials = options.partials; container.decorators = options.decorators; container.hooks = options.hooks; } }; ret._child = function(i, data, blockParams, depths) { if (templateSpec.useBlockParams && !blockParams) { throw new Exception('must pass block params'); } if (templateSpec.useDepths && !depths) { throw new Exception('must pass parent depths'); } return wrapProgram( container, i, templateSpec[i], data, 0, blockParams, depths ); }; return ret; } export function wrapProgram( container, i, fn, data, declaredBlockParams, blockParams, depths ) { function prog(context, options = {}) { let currentDepths = depths; if ( depths && context != depths[0] && !(context === container.nullContext && depths[0] === null) ) { currentDepths = [context].concat(depths); } return fn( container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths ); } prog = executeDecorators(fn, prog, container, depths, data, blockParams); prog.program = i; prog.depth = depths ? depths.length : 0; prog.blockParams = declaredBlockParams || 0; return prog; } /** * This is currently part of the official API, therefore implementation details should not be changed. */ export function resolvePartial(partial, context, options) { if (!partial) { if (options.name === '@partial-block') { partial = options.data['partial-block']; } else { partial = options.partials[options.name]; } } else if (!partial.call && !options.name) { // This is a dynamic partial that returned a string options.name = partial; partial = options.partials[partial]; } return partial; } export function invokePartial(partial, context, options) { // Use the current closure context to save the partial-block if this partial const currentPartialBlock = options.data && options.data['partial-block']; options.partial = true; if (options.ids) { options.data.contextPath = options.ids[0] || options.data.contextPath; } let partialBlock; if (options.fn && options.fn !== noop) { options.data = createFrame(options.data); // Wrapper function to get access to currentPartialBlock from the closure let fn = options.fn; partialBlock = options.data['partial-block'] = function partialBlockWrapper( context, options = {} ) { // Restore the partial-block from the closure for the execution of the block // i.e. the part inside the block of the partial call. options.data = createFrame(options.data); options.data['partial-block'] = currentPartialBlock; return fn(context, options); }; if (fn.partials) { options.partials = Utils.extend({}, options.partials, fn.partials); } } if (partial === undefined && partialBlock) { partial = partialBlock; } if (partial === undefined) { throw new Exception('The partial ' + options.name + ' could not be found'); } else if (partial instanceof Function) { return partial(context, options); } } export function noop() { return ''; } function initData(context, data) { if (!data || !('root' in data)) { data = data ? createFrame(data) : {}; data.root = context; } return data; } function executeDecorators(fn, prog, container, depths, data, blockParams) { if (fn.decorator) { let props = {}; prog = fn.decorator( prog, props, container, depths && depths[0], data, blockParams, depths ); Utils.extend(prog, props); } return prog; } function wrapHelpersToPassLookupProperty(mergedHelpers, container) { Object.keys(mergedHelpers).forEach(helperName => { let helper = mergedHelpers[helperName]; mergedHelpers[helperName] = passLookupPropertyOption(helper, container); }); } function passLookupPropertyOption(helper, container) { const lookupProperty = container.lookupProperty; return wrapHelper(helper, options => { return Utils.extend({ lookupProperty }, options); }); }