summaryrefslogtreecommitdiff
path: root/dev/lib/handlebars/src/visitor.ts
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2024-02-05 06:11:08 -0500
committerGitHub <noreply@github.com>2024-02-05 11:11:08 +0000
commit71c3aff53173cc83a96d7d2715b7918bdbc2d8a5 (patch)
treed13cc4a90ef26bb6476daca1edc1999208f4fadc /dev/lib/handlebars/src/visitor.ts
parent0e7531bc5b443461d7e76e20877464ccf48a3ef5 (diff)
kbn-handlebars dependency update (#613)
* Update kbn-handlebars dependency * Move handlebars dependency to dev * Update package * Update readme * Update readme * Ignore legal file
Diffstat (limited to 'dev/lib/handlebars/src/visitor.ts')
-rw-r--r--dev/lib/handlebars/src/visitor.ts778
1 files changed, 0 insertions, 778 deletions
diff --git a/dev/lib/handlebars/src/visitor.ts b/dev/lib/handlebars/src/visitor.ts
deleted file mode 100644
index 1842c8e5..00000000
--- a/dev/lib/handlebars/src/visitor.ts
+++ /dev/null
@@ -1,778 +0,0 @@
-/*
- * Elasticsearch B.V licenses this file to you under the MIT License.
- * See `packages/kbn-handlebars/LICENSE` for more information.
- */
-
-import Handlebars from 'handlebars';
-import {
- createProtoAccessControl,
- resultIsAllowed,
- // @ts-expect-error: Could not find a declaration file for module
-} from 'handlebars/dist/cjs/handlebars/internal/proto-access';
-// @ts-expect-error: Could not find a declaration file for module
-import AST from 'handlebars/dist/cjs/handlebars/compiler/ast';
-// @ts-expect-error: Could not find a declaration file for module
-import { indexOf, createFrame } from 'handlebars/dist/cjs/handlebars/utils';
-// @ts-expect-error: Could not find a declaration file for module
-import { moveHelperToHooks } from 'handlebars/dist/cjs/handlebars/helpers';
-
-import type {
- AmbiguousHelperOptions,
- CompileOptions,
- Container,
- DecoratorDelegate,
- DecoratorsHash,
- HelperOptions,
- NodeType,
- NonBlockHelperOptions,
- ProcessableBlockStatementNode,
- ProcessableNode,
- ProcessableNodeWithPathParts,
- ProcessableNodeWithPathPartsOrLiteral,
- ProcessableStatementNode,
- ResolvePartialOptions,
- RuntimeOptions,
- Template,
- TemplateDelegate,
- VisitorHelper,
-} from './types';
-import { kAmbiguous, kHelper, kSimple } from './symbols';
-import {
- initData,
- isBlock,
- isDecorator,
- noop,
- toDecoratorOptions,
- transformLiteralToPath,
-} from './utils';
-
-export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
- private env: typeof Handlebars;
- private contexts: any[] = [];
- private output: any[] = [];
- private template?: string;
- private compileOptions: CompileOptions;
- private runtimeOptions?: RuntimeOptions;
- private blockParamNames: any[][] = [];
- private blockParamValues: any[][] = [];
- private ast?: hbs.AST.Program;
- private container: Container;
- private defaultHelperOptions: Pick<NonBlockHelperOptions, 'lookupProperty'>;
- private processedRootDecorators = false; // Root decorators should not have access to input arguments. This flag helps us detect them.
- private processedDecoratorsForProgram = new Set(); // It's important that a given program node only has its decorators run once, we use this Map to keep track of them
-
- constructor(
- env: typeof Handlebars,
- input: string | hbs.AST.Program,
- options: CompileOptions = {}
- ) {
- super();
-
- this.env = env;
-
- if (typeof input !== 'string' && input.type === 'Program') {
- this.ast = input;
- } else {
- this.template = input as string;
- }
-
- this.compileOptions = { data: true, ...options };
- this.compileOptions.knownHelpers = Object.assign(
- Object.create(null),
- {
- helperMissing: true,
- blockHelperMissing: true,
- each: true,
- if: true,
- unless: true,
- with: true,
- log: true,
- lookup: true,
- },
- this.compileOptions.knownHelpers
- );
-
- const protoAccessControl = createProtoAccessControl({});
-
- const container: Container = (this.container = {
- helpers: {},
- partials: {},
- decorators: {},
- strict(obj, name, loc) {
- if (!obj || !(name in obj)) {
- throw new Handlebars.Exception('"' + name + '" not defined in ' + obj, {
- loc,
- } as hbs.AST.Node);
- }
- return container.lookupProperty(obj, name);
- },
- // this function is lifted from the handlebars source and slightly modified (lib/handlebars/runtime.js)
- lookupProperty(parent, propertyName) {
- const result = parent[propertyName];
- if (result == null) {
- return result;
- }
- if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
- return result;
- }
-
- if (resultIsAllowed(result, protoAccessControl, propertyName)) {
- return result;
- }
- return undefined;
- },
- // this function is lifted from the handlebars source and slightly modified (lib/handlebars/runtime.js)
- lambda(current, context) {
- return typeof current === 'function' ? current.call(context) : current;
- },
- data(value: any, depth: number) {
- while (value && depth--) {
- value = value._parent;
- }
- return value;
- },
- hooks: {},
- });
-
- this.defaultHelperOptions = {
- lookupProperty: container.lookupProperty,
- };
- }
-
- render(context: any, options: RuntimeOptions = {}): string {
- this.contexts = [context];
- this.output = [];
- this.runtimeOptions = { ...options };
- this.container.helpers = { ...this.env.helpers, ...options.helpers };
- this.container.partials = { ...this.env.partials, ...options.partials };
- this.container.decorators = {
- ...(this.env.decorators as DecoratorsHash),
- ...options.decorators,
- };
- this.container.hooks = {};
- this.processedRootDecorators = false;
- this.processedDecoratorsForProgram.clear();
-
- if (this.compileOptions.data) {
- this.runtimeOptions.data = initData(context, this.runtimeOptions.data);
- }
-
- const keepHelperInHelpers = false;
- moveHelperToHooks(this.container, 'helperMissing', keepHelperInHelpers);
- moveHelperToHooks(this.container, 'blockHelperMissing', keepHelperInHelpers);
-
- if (!this.ast) {
- this.ast = Handlebars.parse(this.template!);
- }
-
- // The `defaultMain` function contains the default behavior:
- //
- // Generate a "program" function based on the root `Program` in the AST and
- // call it. This will start the processing of all the child nodes in the
- // AST.
- const defaultMain: TemplateDelegate = (_context) => {
- const prog = this.generateProgramFunction(this.ast!);
- return prog(_context, this.runtimeOptions);
- };
-
- // Run any decorators that might exist on the root:
- //
- // The `defaultMain` function is passed in, and if there are no root
- // decorators, or if the decorators chooses to do so, the same function is
- // returned from `processDecorators` and the default behavior is retained.
- //
- // Alternatively any of the root decorators might call the `defaultMain`
- // function themselves, process its return value, and return a completely
- // different `main` function.
- const main = this.processDecorators(this.ast, defaultMain);
- this.processedRootDecorators = true;
-
- // Call the `main` function and add the result to the final output.
- const result = main(this.context, options);
-
- if (main === defaultMain) {
- this.output.push(result);
- return this.output.join('');
- } else {
- // We normally expect the return value of `main` to be a string. However,
- // if a decorator is used to override the `defaultMain` function, the
- // return value can be any type. To match the upstream handlebars project
- // behavior, we want the result of rendering the template to be the
- // literal value returned by the decorator.
- //
- // Since the output array in this case always will be empty, we just
- // return that single value instead of attempting to join all the array
- // elements as strings.
- return result;
- }
- }
-
- // ********************************************** //
- // *** Visitor AST Traversal Functions *** //
- // ********************************************** //
-
- Program(program: hbs.AST.Program) {
- this.blockParamNames.unshift(program.blockParams);
- super.Program(program);
- this.blockParamNames.shift();
- }
-
- MustacheStatement(mustache: hbs.AST.MustacheStatement) {
- this.processStatementOrExpression(mustache);
- }
-
- BlockStatement(block: hbs.AST.BlockStatement) {
- this.processStatementOrExpression(block);
- }
-
- PartialStatement(partial: hbs.AST.PartialStatement) {
- this.invokePartial(partial);
- }
-
- PartialBlockStatement(partial: hbs.AST.PartialBlockStatement) {
- this.invokePartial(partial);
- }
-
- // This space is intentionally left blank: We want to override the Visitor
- // class implementation of this method, but since we handle decorators
- // separately before traversing the nodes, we just want to make this a no-op.
- DecoratorBlock(decorator: hbs.AST.DecoratorBlock) {}
-
- // This space is intentionally left blank: We want to override the Visitor
- // class implementation of this method, but since we handle decorators
- // separately before traversing the nodes, we just want to make this a no-op.
- Decorator(decorator: hbs.AST.Decorator) {}
-
- SubExpression(sexpr: hbs.AST.SubExpression) {
- this.processStatementOrExpression(sexpr);
- }
-
- PathExpression(path: hbs.AST.PathExpression) {
- const blockParamId =
- !path.depth && !AST.helpers.scopedId(path) && this.blockParamIndex(path.parts[0]);
-
- let result;
- if (blockParamId) {
- result = this.lookupBlockParam(blockParamId, path);
- } else if (path.data) {
- result = this.lookupData(this.runtimeOptions!.data, path);
- } else {
- result = this.resolvePath(this.contexts[path.depth], path);
- }
-
- this.output.push(result);
- }
-
- ContentStatement(content: hbs.AST.ContentStatement) {
- this.output.push(content.value);
- }
-
- StringLiteral(string: hbs.AST.StringLiteral) {
- this.output.push(string.value);
- }
-
- NumberLiteral(number: hbs.AST.NumberLiteral) {
- this.output.push(number.value);
- }
-
- BooleanLiteral(bool: hbs.AST.BooleanLiteral) {
- this.output.push(bool.value);
- }
-
- UndefinedLiteral() {
- this.output.push(undefined);
- }
-
- NullLiteral() {
- this.output.push(null);
- }
-
- // ********************************************** //
- // *** Visitor AST Helper Functions *** //
- // ********************************************** //
-
- /**
- * Special code for decorators, since they have to be executed ahead of time (before the wrapping program).
- * So we have to look into the program AST body and see if it contains any decorators that we have to process
- * before we can finish processing of the wrapping program.
- */
- private processDecorators(program: hbs.AST.Program, prog: TemplateDelegate) {
- if (!this.processedDecoratorsForProgram.has(program)) {
- this.processedDecoratorsForProgram.add(program);
- const props = {};
- for (const node of program.body) {
- if (isDecorator(node)) {
- prog = this.processDecorator(node, prog, props);
- }
- }
- }
-
- return prog;
- }
-
- private processDecorator(
- decorator: hbs.AST.DecoratorBlock | hbs.AST.Decorator,
- prog: TemplateDelegate,
- props: Record<string, any>
- ) {
- const options = this.setupDecoratorOptions(decorator);
-
- const result = this.container.lookupProperty<DecoratorDelegate>(
- this.container.decorators,
- options.name
- )(prog, props, this.container, options);
-
- return Object.assign(result || prog, props);
- }
-
- private processStatementOrExpression(node: ProcessableNodeWithPathPartsOrLiteral) {
- // Calling `transformLiteralToPath` has side-effects!
- // It converts a node from type `ProcessableNodeWithPathPartsOrLiteral` to `ProcessableNodeWithPathParts`
- transformLiteralToPath(node);
-
- switch (this.classifyNode(node as ProcessableNodeWithPathParts)) {
- case kSimple:
- this.processSimpleNode(node as ProcessableNodeWithPathParts);
- break;
- case kHelper:
- this.processHelperNode(node as ProcessableNodeWithPathParts);
- break;
- case kAmbiguous:
- this.processAmbiguousNode(node as ProcessableNodeWithPathParts);
- break;
- }
- }
-
- // Liftet from lib/handlebars/compiler/compiler.js (original name: classifySexpr)
- private classifyNode(node: { path: hbs.AST.PathExpression }): NodeType {
- const isSimple = AST.helpers.simpleId(node.path);
- const isBlockParam = isSimple && !!this.blockParamIndex(node.path.parts[0]);
-
- // a mustache is an eligible helper if:
- // * its id is simple (a single part, not `this` or `..`)
- let isHelper = !isBlockParam && AST.helpers.helperExpression(node);
-
- // if a mustache is an eligible helper but not a definite
- // helper, it is ambiguous, and will be resolved in a later
- // pass or at runtime.
- let isEligible = !isBlockParam && (isHelper || isSimple);
-
- // if ambiguous, we can possibly resolve the ambiguity now
- // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
- if (isEligible && !isHelper) {
- const name = node.path.parts[0];
- const options = this.compileOptions;
- if (options.knownHelpers && options.knownHelpers[name]) {
- isHelper = true;
- } else if (options.knownHelpersOnly) {
- isEligible = false;
- }
- }
-
- if (isHelper) {
- return kHelper;
- } else if (isEligible) {
- return kAmbiguous;
- } else {
- return kSimple;
- }
- }
-
- // Liftet from lib/handlebars/compiler/compiler.js
- private blockParamIndex(name: string): [number, any] | undefined {
- for (let depth = 0, len = this.blockParamNames.length; depth < len; depth++) {
- const blockParams = this.blockParamNames[depth];
- const param = blockParams && indexOf(blockParams, name);
- if (blockParams && param >= 0) {
- return [depth, param];
- }
- }
- }
-
- // Looks up the value of `parts` on the given block param and pushes
- // it onto the stack.
- private lookupBlockParam(blockParamId: [number, any], path: hbs.AST.PathExpression) {
- const value = this.blockParamValues[blockParamId[0]][blockParamId[1]];
- return this.resolvePath(value, path, 1);
- }
-
- // Push the data lookup operator
- private lookupData(data: any, path: hbs.AST.PathExpression) {
- if (path.depth) {
- data = this.container.data(data, path.depth);
- }
-
- return this.resolvePath(data, path);
- }
-
- private processSimpleNode(node: ProcessableNodeWithPathParts) {
- const path = node.path;
- // @ts-expect-error strict is not a valid property on PathExpression, but we used in the same way it's also used in the original handlebars
- path.strict = true;
- const result = this.resolveNodes(path)[0];
- const lambdaResult = this.container.lambda(result, this.context);
-
- if (isBlock(node)) {
- this.blockValue(node, lambdaResult);
- } else {
- this.output.push(lambdaResult);
- }
- }
-
- // The purpose of this opcode is to take a block of the form
- // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
- // replace it on the stack with the result of properly
- // invoking blockHelperMissing.
- private blockValue(node: hbs.AST.BlockStatement, value: any) {
- const name = node.path.original;
- const options = this.setupParams(node, name);
-
- const result = this.container.hooks.blockHelperMissing!.call(this.context, value, options);
-
- this.output.push(result);
- }
-
- private processHelperNode(node: ProcessableNodeWithPathParts) {
- const path = node.path;
- const name = path.parts[0];
-
- if (this.compileOptions.knownHelpers && this.compileOptions.knownHelpers[name]) {
- this.invokeKnownHelper(node);
- } else if (this.compileOptions.knownHelpersOnly) {
- throw new Handlebars.Exception(
- 'You specified knownHelpersOnly, but used the unknown helper ' + name,
- node
- );
- } else {
- this.invokeHelper(node);
- }
- }
-
- // This operation is used when the helper is known to exist,
- // so a `helperMissing` fallback is not required.
- private invokeKnownHelper(node: ProcessableNodeWithPathParts) {
- const name = node.path.parts[0];
- const helper = this.setupHelper(node, name);
- // TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
- const result = helper.fn!.call(helper.context, ...helper.params, helper.options);
- this.output.push(result);
- }
-
- // Pops off the helper's parameters, invokes the helper,
- // and pushes the helper's return value onto the stack.
- //
- // If the helper is not found, `helperMissing` is called.
- private invokeHelper(node: ProcessableNodeWithPathParts) {
- const path = node.path;
- const name = path.original;
- const isSimple = AST.helpers.simpleId(path);
- const helper = this.setupHelper(node, name);
-
- const loc = isSimple && helper.fn ? node.loc : path.loc;
- helper.fn = (isSimple && helper.fn) || this.resolveNodes(path)[0];
-
- if (!helper.fn) {
- if (this.compileOptions.strict) {
- helper.fn = this.container.strict(helper.context, name, loc);
- } else {
- helper.fn = this.container.hooks.helperMissing;
- }
- }
-
- // TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
- const result = helper.fn!.call(helper.context, ...helper.params, helper.options);
-
- this.output.push(result);
- }
-
- private invokePartial(partial: hbs.AST.PartialStatement | hbs.AST.PartialBlockStatement) {
- const { params } = partial;
- if (params.length > 1) {
- throw new Handlebars.Exception(
- `Unsupported number of partial arguments: ${params.length}`,
- partial
- );
- }
-
- const isDynamic = partial.name.type === 'SubExpression';
- const name = isDynamic
- ? this.resolveNodes(partial.name).join('')
- : (partial.name as hbs.AST.PathExpression).original;
-
- const options: AmbiguousHelperOptions & ResolvePartialOptions = this.setupParams(partial, name);
- options.helpers = this.container.helpers;
- options.partials = this.container.partials;
- options.decorators = this.container.decorators;
-
- let partialBlock;
- if ('fn' in options && options.fn !== noop) {
- const { fn } = options;
- const currentPartialBlock = options.data?.['partial-block'];
- options.data = createFrame(options.data);
-
- // Wrapper function to get access to currentPartialBlock from the closure
- partialBlock = options.data['partial-block'] = function partialBlockWrapper(
- context: any,
- wrapperOptions: { data?: HelperOptions['data'] } = {}
- ) {
- // Restore the partial-block from the closure for the execution of the block
- // i.e. the part inside the block of the partial call.
- wrapperOptions.data = createFrame(wrapperOptions.data);
- wrapperOptions.data['partial-block'] = currentPartialBlock;
- return fn(context, wrapperOptions);
- };
-
- if (fn.partials) {
- options.partials = { ...options.partials, ...fn.partials };
- }
- }
-
- let context = {};
- if (params.length === 0 && !this.compileOptions.explicitPartialContext) {
- context = this.context;
- } else if (params.length === 1) {
- context = this.resolveNodes(params[0])[0];
- }
-
- if (Object.keys(options.hash).length > 0) {
- // TODO: context can be an array, but maybe never when we have a hash???
- context = Object.assign({}, context, options.hash);
- }
-
- const partialTemplate: Template | undefined =
- this.container.partials[name] ??
- partialBlock ??
- // TypeScript note: We extend ResolvePartialOptions in our types.ts file
- // to fix an error in the upstream type. When calling back into the
- // upstream code, we just cast back to the non-extended type
- Handlebars.VM.resolvePartial(
- undefined,
- undefined,
- options as Handlebars.ResolvePartialOptions
- );
-
- if (partialTemplate === undefined) {
- throw new Handlebars.Exception(`The partial ${name} could not be found`);
- }
-
- let render;
- if (typeof partialTemplate === 'string') {
- render = this.env.compileAST(partialTemplate, this.compileOptions);
- if (name in this.container.partials) {
- this.container.partials[name] = render;
- }
- } else {
- render = partialTemplate;
- }
-
- let result = render(context, options);
-
- if ('indent' in partial) {
- result =
- partial.indent +
- (this.compileOptions.preventIndent
- ? result
- : result.replace(/\n(?!$)/g, `\n${partial.indent}`)); // indent each line, ignoring any trailing linebreak
- }
-
- this.output.push(result);
- }
-
- private processAmbiguousNode(node: ProcessableNodeWithPathParts) {
- const name = node.path.parts[0];
- const helper = this.setupHelper(node, name);
- let { fn: helperFn } = helper;
-
- const loc = helperFn ? node.loc : node.path.loc;
- helperFn = helperFn ?? this.resolveNodes(node.path)[0];
-
- if (helperFn === undefined) {
- if (this.compileOptions.strict) {
- helperFn = this.container.strict(helper.context, name, loc);
- } else {
- helperFn =
- helper.context != null
- ? this.container.lookupProperty(helper.context, name)
- : helper.context;
- if (helperFn == null) helperFn = this.container.hooks.helperMissing;
- }
- }
-
- const helperResult =
- typeof helperFn === 'function'
- ? helperFn.call(helper.context, ...helper.params, helper.options)
- : helperFn;
-
- if (isBlock(node)) {
- const result = helper.fn
- ? helperResult
- : this.container.hooks.blockHelperMissing!.call(this.context, helperResult, helper.options);
- if (result != null) {
- this.output.push(result);
- }
- } else {
- if (
- (node as hbs.AST.MustacheStatement).escaped === false ||
- this.compileOptions.noEscape === true ||
- typeof helperResult !== 'string'
- ) {
- this.output.push(helperResult);
- } else {
- this.output.push(Handlebars.escapeExpression(helperResult));
- }
- }
- }
-
- private setupHelper(node: ProcessableNode, helperName: string): VisitorHelper {
- return {
- fn: this.container.lookupProperty(this.container.helpers, helperName),
- context: this.context,
- params: this.resolveNodes(node.params),
- options: this.setupParams(node, helperName),
- };
- }
-
- private setupDecoratorOptions(decorator: hbs.AST.Decorator | hbs.AST.DecoratorBlock) {
- // TypeScript: The types indicate that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too.
- const name = (decorator.path as hbs.AST.PathExpression).original;
- const options = toDecoratorOptions(this.setupParams(decorator, name));
-
- if (decorator.params.length > 0) {
- if (!this.processedRootDecorators) {
- // When processing the root decorators, temporarily remove the root context so it's not accessible to the decorator
- const context = this.contexts.shift();
- options.args = this.resolveNodes(decorator.params);
- this.contexts.unshift(context);
- } else {
- options.args = this.resolveNodes(decorator.params);
- }
- } else {
- options.args = [];
- }
-
- return options;
- }
-
- private setupParams(node: ProcessableBlockStatementNode, name: string): HelperOptions;
- private setupParams(node: ProcessableStatementNode, name: string): NonBlockHelperOptions;
- private setupParams(node: ProcessableNode, name: string): AmbiguousHelperOptions;
- private setupParams(node: ProcessableNode, name: string) {
- const options: AmbiguousHelperOptions = {
- name,
- hash: this.getHash(node),
- data: this.runtimeOptions!.data,
- loc: { start: node.loc.start, end: node.loc.end },
- ...this.defaultHelperOptions,
- };
-
- if (isBlock(node)) {
- (options as HelperOptions).fn = node.program
- ? this.processDecorators(node.program, this.generateProgramFunction(node.program))
- : noop;
- (options as HelperOptions).inverse = node.inverse
- ? this.processDecorators(node.inverse, this.generateProgramFunction(node.inverse))
- : noop;
- }
-
- return options;
- }
-
- private generateProgramFunction(program: hbs.AST.Program) {
- if (!program) return noop;
-
- const prog: TemplateDelegate = (nextContext: any, runtimeOptions: RuntimeOptions = {}) => {
- runtimeOptions = { ...runtimeOptions };
-
- // inherit data in blockParams from parent program
- runtimeOptions.data = runtimeOptions.data || this.runtimeOptions!.data;
- if (runtimeOptions.blockParams) {
- runtimeOptions.blockParams = runtimeOptions.blockParams.concat(
- this.runtimeOptions!.blockParams
- );
- }
-
- // inherit partials from parent program
- runtimeOptions.partials = runtimeOptions.partials || this.runtimeOptions!.partials;
-
- // stash parent program data
- const tmpRuntimeOptions = this.runtimeOptions;
- this.runtimeOptions = runtimeOptions;
- const shiftContext = nextContext !== this.context;
- if (shiftContext) this.contexts.unshift(nextContext);
- this.blockParamValues.unshift(runtimeOptions.blockParams || []);
-
- // execute child program
- const result = this.resolveNodes(program).join('');
-
- // unstash parent program data
- this.blockParamValues.shift();
- if (shiftContext) this.contexts.shift();
- this.runtimeOptions = tmpRuntimeOptions;
-
- // return result of child program
- return result;
- };
-
- prog.blockParams = program.blockParams?.length ?? 0;
- return prog;
- }
-
- private getHash(statement: { hash?: hbs.AST.Hash }) {
- const result: { [key: string]: any } = {};
- if (!statement.hash) return result;
- for (const { key, value } of statement.hash.pairs) {
- result[key] = this.resolveNodes(value)[0];
- }
- return result;
- }
-
- private resolvePath(obj: any, path: hbs.AST.PathExpression, index = 0) {
- if (this.compileOptions.strict || this.compileOptions.assumeObjects) {
- return this.strictLookup(obj, path);
- }
-
- for (; index < path.parts.length; index++) {
- if (obj == null) return;
- obj = this.container.lookupProperty(obj, path.parts[index]);
- }
-
- return obj;
- }
-
- private strictLookup(obj: any, path: hbs.AST.PathExpression) {
- // @ts-expect-error strict is not a valid property on PathExpression, but we used in the same way it's also used in the original handlebars
- const requireTerminal = this.compileOptions.strict && path.strict;
- const len = path.parts.length - (requireTerminal ? 1 : 0);
-
- for (let i = 0; i < len; i++) {
- obj = this.container.lookupProperty(obj, path.parts[i]);
- }
-
- if (requireTerminal) {
- return this.container.strict(obj, path.parts[len], path.loc);
- } else {
- return obj;
- }
- }
-
- private resolveNodes(nodes: hbs.AST.Node | hbs.AST.Node[]): any[] {
- const currentOutput = this.output;
- this.output = [];
-
- if (Array.isArray(nodes)) {
- this.acceptArray(nodes);
- } else {
- this.accept(nodes);
- }
-
- const result = this.output;
-
- this.output = currentOutput;
-
- return result;
- }
-
- private get context() {
- return this.contexts[0];
- }
-}