[@kbn/handlebars] Refactor types (#150520)

This commit is contained in:
Thomas Watson 2023-02-14 13:37:41 +01:00 committed by GitHub
parent 50b83014a3
commit 77ed48a75a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 304 additions and 216 deletions

View file

@ -11,6 +11,7 @@
*/
import Handlebars from '.';
import type { HelperOptions, TemplateDelegate } from './src/types';
import { expectTemplate, forEachCompileFunctionName } from './src/__jest__/test_bench';
it('Handlebars.create', () => {
@ -419,7 +420,7 @@ describe('blocks', () => {
.withInput({ me: 'my' })
.withDecorator(
'decorator',
(fn): Handlebars.TemplateDelegate =>
(fn): TemplateDelegate =>
(context, options) => {
expect(context).toMatchInlineSnapshot(`
Object {
@ -446,7 +447,7 @@ describe('blocks', () => {
.withInput({ arr: ['my'] })
.withDecorator(
'decorator',
(fn): Handlebars.TemplateDelegate =>
(fn): TemplateDelegate =>
(context, options) => {
expect(context).toMatchInlineSnapshot(`"my"`);
expect(options).toMatchInlineSnapshot(`
@ -483,12 +484,12 @@ describe('blocks', () => {
it('decorator nested inside of custom helper', () => {
expectTemplate('{{#helper}}{{*decorator}}world{{/helper}}')
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return options.fn('my', { foo: 'bar' } as any);
})
.withDecorator(
'decorator',
(fn): Handlebars.TemplateDelegate =>
(fn): TemplateDelegate =>
(context, options) => {
expect(context).toMatchInlineSnapshot(`"my"`);
expect(options).toMatchInlineSnapshot(`
@ -519,7 +520,7 @@ describe('blocks', () => {
})
.withDecorator('decorator', (fn) => {
const decoratorCallOrder = ++decoratorCall;
const ret: Handlebars.TemplateDelegate = () => {
const ret: TemplateDelegate = () => {
const progCallOrder = ++progCall;
return `(decorator: ${decoratorCallOrder}, prog: ${progCallOrder}, fn: "${fn()}")`;
};

View file

@ -24,7 +24,10 @@ export default Handlebars;
export const compileFnName: 'compile' | 'compileAST' = allowUnsafeEval() ? 'compile' : 'compileAST';
export type {
DecoratorFunction,
ExtendedCompileOptions,
ExtendedRuntimeOptions,
CompileOptions,
RuntimeOptions,
HelperDelegate,
TemplateDelegate,
DecoratorDelegate,
HelperOptions,
} from './src/types';

View file

@ -3,13 +3,13 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import type {
DecoratorFunction,
DecoratorsHash,
ExtendedCompileOptions,
ExtendedRuntimeOptions,
} from '../types';
import Handlebars, {
type CompileOptions,
type DecoratorDelegate,
type HelperDelegate,
type RuntimeOptions,
} from '../..';
import type { DecoratorsHash, HelpersHash, PartialsHash, Template } from '../types';
type CompileFns = 'compile' | 'compileAST';
const compileFns: CompileFns[] = ['compile', 'compileAST'];
@ -40,10 +40,10 @@ export function forEachCompileFunctionName(
class HandlebarsTestBench {
private template: string;
private options: TestOptions;
private compileOptions?: ExtendedCompileOptions;
private runtimeOptions?: ExtendedRuntimeOptions;
private helpers: { [name: string]: Handlebars.HelperDelegate | undefined } = {};
private partials: { [name: string]: Handlebars.Template } = {};
private compileOptions?: CompileOptions;
private runtimeOptions?: RuntimeOptions;
private helpers: HelpersHash = {};
private partials: PartialsHash = {};
private decorators: DecoratorsHash = {};
private input: any = {};
@ -52,12 +52,12 @@ class HandlebarsTestBench {
this.options = options;
}
withCompileOptions(compileOptions?: ExtendedCompileOptions) {
withCompileOptions(compileOptions?: CompileOptions) {
this.compileOptions = compileOptions;
return this;
}
withRuntimeOptions(runtimeOptions?: ExtendedRuntimeOptions) {
withRuntimeOptions(runtimeOptions?: RuntimeOptions) {
this.runtimeOptions = runtimeOptions;
return this;
}
@ -67,36 +67,36 @@ class HandlebarsTestBench {
return this;
}
withHelper<F extends Handlebars.HelperDelegate>(name: string, helper?: F) {
withHelper<F extends HelperDelegate>(name: string, helper: F) {
this.helpers[name] = helper;
return this;
}
withHelpers<F extends Handlebars.HelperDelegate>(helperFunctions: { [name: string]: F }) {
withHelpers<F extends HelperDelegate>(helperFunctions: Record<string, F>) {
for (const [name, helper] of Object.entries(helperFunctions)) {
this.withHelper(name, helper);
}
return this;
}
withPartial(name: string | number, partial: Handlebars.Template) {
withPartial(name: string | number, partial: Template) {
this.partials[name] = partial;
return this;
}
withPartials(partials: { [name: string]: Handlebars.Template }) {
withPartials(partials: Record<string, Template>) {
for (const [name, partial] of Object.entries(partials)) {
this.withPartial(name, partial);
}
return this;
}
withDecorator(name: string, decoratorFunction: DecoratorFunction) {
withDecorator(name: string, decoratorFunction: DecoratorDelegate) {
this.decorators[name] = decoratorFunction;
return this;
}
withDecorators(decoratorFunctions: { [key: string]: DecoratorFunction }) {
withDecorators(decoratorFunctions: Record<string, DecoratorDelegate>) {
for (const [name, decoratorFunction] of Object.entries(decoratorFunctions)) {
this.withDecorator(name, decoratorFunction);
}
@ -154,9 +154,9 @@ class HandlebarsTestBench {
private compileAndExecuteEval() {
const renderEval = this.compileEval();
const runtimeOptions: ExtendedRuntimeOptions = {
helpers: this.helpers as Record<string, Function>,
partials: this.partials as Record<string, HandlebarsTemplateDelegate>,
const runtimeOptions: RuntimeOptions = {
helpers: this.helpers,
partials: this.partials,
decorators: this.decorators,
...this.runtimeOptions,
};
@ -169,9 +169,9 @@ class HandlebarsTestBench {
private compileAndExecuteAST() {
const renderAST = this.compileAST();
const runtimeOptions: ExtendedRuntimeOptions = {
helpers: this.helpers as Record<string, Function>,
partials: this.partials as Record<string, HandlebarsTemplateDelegate>,
const runtimeOptions: RuntimeOptions = {
helpers: this.helpers,
partials: this.partials,
decorators: this.decorators,
...this.runtimeOptions,
};

View file

@ -7,7 +7,7 @@
// https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require
import Handlebars from 'handlebars';
import type { ExtendedCompileOptions, ExtendedRuntimeOptions } from './types';
import type { CompileOptions, RuntimeOptions, TemplateDelegate } from './types';
import { ElasticHandlebarsVisitor } from './visitor';
const originalCreate = Handlebars.create;
@ -30,15 +30,10 @@ Handlebars.create = function (): typeof Handlebars {
return SandboxedHandlebars;
};
/**
* Compiles the given Handlbars template without the use of `eval`.
*
* @returns A render function with the same API as the return value from the regular Handlebars `compile` function.
*/
Handlebars.compileAST = function (
input: string | hbs.AST.Program,
options?: ExtendedCompileOptions
) {
options?: CompileOptions
): TemplateDelegate {
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
throw new Handlebars.Exception(
`You must pass a string or Handlebars AST to Handlebars.compileAST. You passed ${input}`
@ -48,6 +43,5 @@ Handlebars.compileAST = function (
// If `Handlebars.compileAST` is reassigned, `this` will be undefined.
const visitor = new ElasticHandlebarsVisitor(this ?? Handlebars, input, options);
return (context: any, runtimeOptions?: ExtendedRuntimeOptions) =>
visitor.render(context, runtimeOptions);
return (context: any, runtimeOptions?: RuntimeOptions) => visitor.render(context, runtimeOptions);
};

View file

@ -5,7 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import Handlebars, { type HelperOptions } from '../..';
import { expectTemplate } from '../__jest__/test_bench';
describe('blocks', () => {
@ -200,7 +200,7 @@ describe('blocks', () => {
describe('decorators', () => {
it('should apply mustache decorators', () => {
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn) {
@ -212,7 +212,7 @@ describe('blocks', () => {
it('should apply allow undefined return', () => {
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return options.fn() + (options.fn as any).run;
})
.withDecorator('decorator', function (fn) {
@ -223,7 +223,7 @@ describe('blocks', () => {
it('should apply block decorators', () => {
expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}')
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn, props, container, options) {
@ -237,7 +237,7 @@ describe('blocks', () => {
expectTemplate(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return (options.fn as any).run;
})
.withDecorators({
@ -256,7 +256,7 @@ describe('blocks', () => {
expectTemplate(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn, props, container, options) {
@ -268,7 +268,7 @@ describe('blocks', () => {
it('should access parent variables', () => {
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
.withHelper('helper', function (options: Handlebars.HelperOptions) {
.withHelper('helper', function (options: HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn, props, container, options) {

View file

@ -5,7 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import Handlebars, { type HelperOptions } from '../..';
import { expectTemplate } from '../__jest__/test_bench';
describe('data', () => {
@ -30,7 +30,7 @@ describe('data', () => {
global.kbnHandlebarsEnv = Handlebars.create();
const helpers = Handlebars.createFrame(kbnHandlebarsEnv!.helpers);
helpers.let = function (options: Handlebars.HelperOptions) {
helpers.let = function (options: HelperOptions) {
const frame = Handlebars.createFrame(options.data);
for (const prop in options.hash) {
@ -138,7 +138,7 @@ describe('data', () => {
expectTemplate('{{>myPartial}}')
.withCompileOptions({ data: true })
.withPartial('myPartial', '{{hello}}')
.withHelper('hello', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('hello', function (this: any, options: HelperOptions) {
return options.data.adjective + ' ' + this.noun;
})
.withInput({ noun: 'cat' })

View file

@ -5,7 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import Handlebars, { type HelperOptions } from '../..';
import { expectTemplate } from '../__jest__/test_bench';
beforeEach(() => {
@ -32,7 +32,7 @@ describe('helpers', () => {
it('helper for raw block gets raw content', () => {
expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}')
.withInput({ test: 'hello' })
.withHelper('raw', function (options: Handlebars.HelperOptions) {
.withHelper('raw', function (options: HelperOptions) {
return options.fn();
})
.toCompileTo(' {{test}} ');
@ -41,7 +41,7 @@ describe('helpers', () => {
it('helper for raw block gets parameters', () => {
expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}')
.withInput({ test: 'hello' })
.withHelper('raw', function (a, b, c, options: Handlebars.HelperOptions) {
.withHelper('raw', function (a, b, c, options: HelperOptions) {
const ret = options.fn() + a + b + c;
return ret;
})
@ -51,7 +51,7 @@ describe('helpers', () => {
describe('raw block parsing (with identity helper-function)', () => {
function runWithIdentityHelper(template: string, expected: string) {
expectTemplate(template)
.withHelper('identity', function (options: Handlebars.HelperOptions) {
.withHelper('identity', function (options: HelperOptions) {
return options.fn();
})
.toCompileTo(expected);
@ -95,7 +95,7 @@ describe('helpers', () => {
it('helper block with identical context', () => {
expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}')
.withInput({ name: 'Alan' })
.withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (this: any, options: HelperOptions) {
let out = '';
const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
for (let i = 0, j = byes.length; i < j; i++) {
@ -109,7 +109,7 @@ describe('helpers', () => {
it('helper block with complex lookup expression', () => {
expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}')
.withInput({ name: 'Alan' })
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
let out = '';
const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
for (let i = 0, j = byes.length; i < j; i++) {
@ -126,7 +126,7 @@ describe('helpers', () => {
prefix: '/root',
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }],
})
.withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
.withHelper('link', function (this: any, prefix, options: HelperOptions) {
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
})
.toCompileTo('<a href="/root/goodbye">Goodbye</a>');
@ -138,7 +138,7 @@ describe('helpers', () => {
prefix: '/root',
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }],
})
.withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
.withHelper('link', function (this: any, prefix, options: HelperOptions) {
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
})
.toCompileTo('<a href="/root/goodbye">Goodbye</a>');
@ -161,7 +161,7 @@ describe('helpers', () => {
it('block helper', () => {
expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!')
.withInput({ world: 'world' })
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
return options.fn({ text: 'GOODBYE' });
})
.toCompileTo('GOODBYE! cruel world!');
@ -170,14 +170,14 @@ describe('helpers', () => {
it('block helper staying in the same context', () => {
expectTemplate('{{#form}}<p>{{name}}</p>{{/form}}')
.withInput({ name: 'Yehuda' })
.withHelper('form', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('form', function (this: any, options: HelperOptions) {
return '<form>' + options.fn(this) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p></form>');
});
it('block helper should have context in this', () => {
function link(this: any, options: Handlebars.HelperOptions) {
function link(this: any, options: HelperOptions) {
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
}
@ -201,7 +201,7 @@ describe('helpers', () => {
it('block helper passing a new context', () => {
expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{/form}}')
.withInput({ yehuda: { name: 'Yehuda' } })
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
.withHelper('form', function (context, options: HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p></form>');
@ -210,7 +210,7 @@ describe('helpers', () => {
it('block helper passing a complex path context', () => {
expectTemplate('{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}')
.withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } })
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
.withHelper('form', function (context, options: HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Harold</p></form>');
@ -221,10 +221,10 @@ describe('helpers', () => {
.withInput({
yehuda: { name: 'Yehuda' },
})
.withHelper('link', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('link', function (this: any, options: HelperOptions) {
return '<a href="' + this.name + '">' + options.fn(this) + '</a>';
})
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
.withHelper('form', function (context, options: HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>');
@ -232,7 +232,7 @@ describe('helpers', () => {
it('block helper inverted sections', () => {
const string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
function list(this: any, context: any, options: Handlebars.HelperOptions) {
function list(this: any, context: any, options: HelperOptions) {
if (context.length > 0) {
let out = '<ul>';
for (let i = 0, j = context.length; i < j; i++) {
@ -477,7 +477,7 @@ describe('helpers', () => {
it('block multi-params work', () => {
expectTemplate('Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}')
.withInput({ cruel: 'cruel', world: 'world' })
.withHelper('goodbye', function (cruel, world, options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (cruel, world, options: HelperOptions) {
return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world });
})
.toCompileTo('Message: Goodbye cruel world');
@ -487,7 +487,7 @@ describe('helpers', () => {
describe('hash', () => {
it('helpers can take an optional hash', () => {
expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}')
.withHelper('goodbye', function (options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (options: HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -502,7 +502,7 @@ describe('helpers', () => {
});
it('helpers can take an optional hash with booleans', () => {
function goodbye(options: Handlebars.HelperOptions) {
function goodbye(options: HelperOptions) {
if (options.hash.print === true) {
return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world;
} else if (options.hash.print === false) {
@ -523,7 +523,7 @@ describe('helpers', () => {
it('block helpers can take an optional hash', () => {
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (this: any, options: HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -539,7 +539,7 @@ describe('helpers', () => {
it('block helpers can take an optional hash with single quoted stings', () => {
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (this: any, options: HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -554,7 +554,7 @@ describe('helpers', () => {
});
it('block helpers can take an optional hash with booleans', () => {
function goodbye(this: any, options: Handlebars.HelperOptions) {
function goodbye(this: any, options: HelperOptions) {
if (options.hash.print === true) {
return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this);
} else if (options.hash.print === false) {
@ -582,7 +582,7 @@ describe('helpers', () => {
it('if a context is not found, custom helperMissing is used', () => {
expectTemplate('{{hello}} {{link_to world}}')
.withInput({ hello: 'Hello', world: 'world' })
.withHelper('helperMissing', function (mesg, options: Handlebars.HelperOptions) {
.withHelper('helperMissing', function (mesg, options: HelperOptions) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('<a>' + mesg + '</a>');
}
@ -593,7 +593,7 @@ describe('helpers', () => {
it('if a value is not found, custom helperMissing is used', () => {
expectTemplate('{{hello}} {{link_to}}')
.withInput({ hello: 'Hello', world: 'world' })
.withHelper('helperMissing', function (options: Handlebars.HelperOptions) {
.withHelper('helperMissing', function (options: HelperOptions) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('<a>winning</a>');
}
@ -788,7 +788,7 @@ describe('helpers', () => {
it('helpers take precedence over same-named context properties$', () => {
expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}')
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (this: any, options: HelperOptions) {
return this.goodbye.toUpperCase() + options.fn(this);
})
.withHelper('cruel', function (world) {
@ -818,7 +818,7 @@ describe('helpers', () => {
it('Scoped names take precedence over block helpers', () => {
expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}')
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbye', function (this: any, options: HelperOptions) {
return this.goodbye.toUpperCase() + options.fn(this);
})
.withHelper('cruel', function (world) {
@ -836,7 +836,7 @@ describe('helpers', () => {
it('should take presedence over context values', () => {
expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}')
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
})
@ -848,7 +848,7 @@ describe('helpers', () => {
.withHelper('value', function () {
return 'foo';
})
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({}, { blockParams: [1, 2] });
})
@ -861,7 +861,7 @@ describe('helpers', () => {
.withHelper('value', function () {
return 'foo';
})
.withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (this: any, options: HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn(this, { blockParams: [1, 2] });
})
@ -879,7 +879,7 @@ describe('helpers', () => {
}
)
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
return options.fn(
{ value: 'bar' },
{
@ -893,7 +893,7 @@ describe('helpers', () => {
it('should allow block params on chained helpers', () => {
expectTemplate('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}')
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
.withHelper('goodbyes', function (options: HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
})
@ -942,12 +942,9 @@ describe('helpers', () => {
describe('the lookupProperty-option', () => {
it('should be passed to custom helpers', () => {
expectTemplate('{{testHelper}}')
.withHelper(
'testHelper',
function testHelper(this: any, options: Handlebars.HelperOptions) {
return options.lookupProperty(this, 'testProperty');
}
)
.withHelper('testHelper', function testHelper(this: any, options: HelperOptions) {
return options.lookupProperty(this, 'testProperty');
})
.withInput({ testProperty: 'abc' })
.toCompileTo('abc');
});

View file

@ -163,8 +163,7 @@ describe('partials', () => {
global.kbnHandlebarsEnv = Handlebars.create();
expect(() => {
const undef: unknown = undefined;
kbnHandlebarsEnv!.registerPartial('undefined_test', undef as Handlebars.Template);
kbnHandlebarsEnv!.registerPartial('undefined_test', undefined as any);
}).toThrow('Attempting to register a partial called "undefined_test" as undefined');
global.kbnHandlebarsEnv = null;
@ -294,7 +293,6 @@ describe('partials', () => {
{},
{
partials: {
// @ts-expect-error
dude: 'fail',
},
}

View file

@ -5,7 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import Handlebars, { type HelperOptions } from '../..';
import { expectTemplate, forEachCompileFunctionName } from '../__jest__/test_bench';
describe('Regressions', () => {
@ -99,10 +99,10 @@ describe('Regressions', () => {
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
const helpers = {
blk(block: Handlebars.HelperOptions) {
blk(block: HelperOptions) {
return block.fn('');
},
inverse(block: Handlebars.HelperOptions) {
inverse(block: HelperOptions) {
return block.inverse('');
},
};
@ -204,7 +204,7 @@ describe('Regressions', () => {
it('GH-1054: Should handle simple safe string responses', () => {
expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}')
.withHelpers({
wrap(options: Handlebars.HelperOptions) {
wrap(options: HelperOptions) {
return new Handlebars.SafeString(options.fn());
},
})
@ -279,7 +279,7 @@ describe('Regressions', () => {
)
.withInput({ array: [1], name: 'John' })
.withHelpers({
myif(conditional, options: Handlebars.HelperOptions) {
myif(conditional, options: HelperOptions) {
if (conditional) {
return options.fn(this);
} else {
@ -325,7 +325,7 @@ describe('Regressions', () => {
expectTemplate('{{helpa length="foo"}}')
.withInput({ array: [1], name: 'John' })
.withHelpers({
helpa(options: Handlebars.HelperOptions) {
helpa(options: HelperOptions) {
return options.hash.length;
},
})
@ -371,7 +371,7 @@ describe('Regressions', () => {
describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", () => {
it('should treat undefined helpers like non-existing helpers', () => {
expectTemplate('{{foo}}')
.withHelper('foo', undefined)
.withHelper('foo', undefined as any)
.withInput({ foo: 'bar' })
.toCompileTo('bar');
});

View file

@ -5,7 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import Handlebars, { type HelperOptions } from '../..';
import { expectTemplate } from '../__jest__/test_bench';
describe('subexpressions', () => {
@ -102,9 +102,9 @@ describe('subexpressions', () => {
});
it('provides each nested helper invocation its own options hash', () => {
let lastOptions: Handlebars.HelperOptions;
let lastOptions: HelperOptions;
const helpers = {
equal(x: any, y: any, options: Handlebars.HelperOptions) {
equal(x: any, y: any, options: HelperOptions) {
if (!options || options === lastOptions) {
throw new Error('options hash was reused');
}

View file

@ -5,66 +5,67 @@
import { kHelper, kAmbiguous, kSimple } from './symbols';
// Unexported `CompileOptions` lifted from node_modules/handlebars/types/index.d.ts
// While it could also be extracted using `NonNullable<Parameters<typeof Handlebars.compile>[1]>`, this isn't possible since we declare the handlebars module below
interface HandlebarsCompileOptions {
data?: boolean;
compat?: boolean;
knownHelpers?: KnownHelpers;
knownHelpersOnly?: boolean;
noEscape?: boolean;
strict?: boolean;
assumeObjects?: boolean;
preventIndent?: boolean;
ignoreStandalone?: boolean;
explicitPartialContext?: boolean;
}
/**
* A custom version of the Handlesbars module with an extra `compileAST` function and fixed typings.
* A custom version of the Handlebars module with an extra `compileAST` function and fixed typings.
*/
declare module 'handlebars' {
/**
* Compiles the given Handlebars template without the use of `eval`.
*
* @returns A render function with the same API as the return value from the regular Handlebars `compile` function.
*/
export function compileAST(
input: string | hbs.AST.Program,
options?: ExtendedCompileOptions
): (context?: any, options?: ExtendedRuntimeOptions) => string;
options?: CompileOptions
): TemplateDelegateFixed;
// --------------------------------------------------------
// Override/Extend inherited types below that are incorrect
// Override/Extend inherited funcions and interfaces below that are incorrect.
//
// Any exported `const` or `type` types can't be overwritten, so we'll just
// have to live with those and cast them to the correct types in our code.
// Some of these fixed types, we'll instead export outside the scope of this
// 'handlebars' module so consumers of @kbn/handlebars at least have a way to
// access the correct types.
// --------------------------------------------------------
export interface TemplateDelegate<T = any> {
(context?: T, options?: RuntimeOptions): string; // Override to ensure `context` is optional
blockParams?: number; // TODO: Can this really be optional?
partials?: any; // TODO: Narrow type to something better than any?
}
/**
* A {@link https://handlebarsjs.com/api-reference/helpers.html helper-function} type.
*
* When registering a helper function, it should be of this type.
*/
export interface HelperDelegate extends HelperDelegateFixed {} // eslint-disable-line @typescript-eslint/no-empty-interface
export interface HelperOptions {
name: string;
loc: { start: hbs.AST.SourceLocation['start']; end: hbs.AST.SourceLocation['end'] };
lookupProperty: LookupProperty;
}
/**
* A template-function type.
*
* This type is primarily used for the return value of by calls to
* {@link https://handlebarsjs.com/api-reference/compilation.html#handlebars-compile-template-options Handlebars.compile},
* Handlebars.compileAST and {@link https://handlebarsjs.com/api-reference/compilation.html#handlebars-precompile-template-options Handlebars.template}.
*/
export interface TemplateDelegate<T = any> extends TemplateDelegateFixed<T> {} // eslint-disable-line @typescript-eslint/no-empty-interface
export interface HelperDelegate {
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(...params: any[]): any;
}
export function registerPartial(spec: { [name: string]: Handlebars.Template }): void; // Ensure `spec` object values can be strings
}
export type NodeType = typeof kHelper | typeof kAmbiguous | typeof kSimple;
type LookupProperty = <T = any>(parent: { [name: string]: any }, propertyName: string) => T;
export type ProcessableStatementNode =
| hbs.AST.MustacheStatement
| hbs.AST.PartialStatement
| hbs.AST.SubExpression;
export type ProcessableBlockStatementNode = hbs.AST.BlockStatement | hbs.AST.PartialBlockStatement;
export type ProcessableNode = ProcessableStatementNode | ProcessableBlockStatementNode;
export type ProcessableNodeWithPathParts = ProcessableNode & { path: hbs.AST.PathExpression };
export type ProcessableNodeWithPathPartsOrLiteral = ProcessableNode & {
path: hbs.AST.PathExpression | hbs.AST.Literal;
};
export interface Helper {
fn?: Handlebars.HelperDelegate;
context: any[];
params: any[];
options: AmbiguousHelperOptions;
}
export type NonBlockHelperOptions = Omit<Handlebars.HelperOptions, 'fn' | 'inverse'>;
export type AmbiguousHelperOptions = Handlebars.HelperOptions | NonBlockHelperOptions;
export interface DecoratorOptions extends Omit<Handlebars.HelperOptions, 'lookupProperties'> {
args?: any[];
/**
* Register one or more {@link https://handlebarsjs.com/api-reference/runtime.html#handlebars-registerpartial-name-partial partials}.
*
* @param spec A key/value object where each key is the name of a partial (a string) and each value is the partial (either a string or a partial function).
*/
export function registerPartial(spec: Record<string, TemplateFixed>): void; // Ensure `spec` object values can be strings
}
/**
@ -73,8 +74,8 @@ export interface DecoratorOptions extends Omit<Handlebars.HelperOptions, 'lookup
* This is a subset of all the compile options supported by the upstream
* Handlebars module.
*/
export type ExtendedCompileOptions = Pick<
CompileOptions,
export type CompileOptions = Pick<
HandlebarsCompileOptions,
| 'data'
| 'knownHelpers'
| 'knownHelpersOnly'
@ -91,46 +92,134 @@ export type ExtendedCompileOptions = Pick<
* This is a subset of all the runtime options supported by the upstream
* Handlebars module.
*/
export type ExtendedRuntimeOptions = Pick<
RuntimeOptions,
'data' | 'helpers' | 'partials' | 'decorators' | 'blockParams'
>;
export interface RuntimeOptions extends Pick<Handlebars.RuntimeOptions, 'data' | 'blockParams'> {
// The upstream `helpers` property is too loose and allows all functions.
helpers?: HelpersHash;
// The upstream `partials` property is incorrectly typed and doesn't allow
// partials to be strings.
partials?: PartialsHash;
// The upstream `decorators` property is too loose and allows all functions.
decorators?: DecoratorsHash;
}
/**
* According to the [decorator docs]{@link https://github.com/handlebars-lang/handlebars.js/blob/4.x/docs/decorators-api.md},
* a decorator will be called with a different set of arugments than what's actually happening in the upstream code.
* So here I assume that the docs are wrong and that the upstream code is correct. In reality, `context` is the last 4
* documented arguments rolled into one object.
* The last argument being passed to a helper function is a an {@link https://handlebarsjs.com/api-reference/helpers.html#the-options-parameter options object}.
*/
export type DecoratorFunction = (
prog: Handlebars.TemplateDelegate,
export interface HelperOptions extends Omit<Handlebars.HelperOptions, 'fn' | 'inverse'> {
name: string;
fn: TemplateDelegateFixed;
inverse: TemplateDelegateFixed;
loc: { start: hbs.AST.SourceLocation['start']; end: hbs.AST.SourceLocation['end'] };
lookupProperty: LookupProperty;
}
// Use the post-fix `Fixed` to allow us to acces it inside the 'handlebars' module declared above
/**
* A {@link https://handlebarsjs.com/api-reference/helpers.html helper-function} type.
*
* When registering a helper function, it should be of this type.
*/
interface HelperDelegateFixed {
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(...params: any[]): any;
}
export type { HelperDelegateFixed as HelperDelegate };
// Use the post-fix `Fixed` to allow us to acces it inside the 'handlebars' module declared above
/**
* A template-function type.
*
* This type is primarily used for the return value of by calls to
* {@link https://handlebarsjs.com/api-reference/compilation.html#handlebars-compile-template-options Handlebars.compile},
* Handlebars.compileAST and {@link https://handlebarsjs.com/api-reference/compilation.html#handlebars-precompile-template-options Handlebars.template}.
*/
interface TemplateDelegateFixed<T = any> {
(context?: T, options?: RuntimeOptions): string; // Override to ensure `context` is optional
blockParams?: number; // TODO: Can this really be optional?
partials?: PartialsHash;
}
export type { TemplateDelegateFixed as TemplateDelegate };
// According to the decorator docs
// (https://github.com/handlebars-lang/handlebars.js/blob/4.x/docs/decorators-api.md)
// a decorator will be called with a different set of arugments than what's
// actually happening in the upstream code. So here I assume that the docs are
// wrong and that the upstream code is correct. In reality, `context` is the
// last 4 documented arguments rolled into one object.
/**
* A {@link https://github.com/handlebars-lang/handlebars.js/blob/master/docs/decorators-api.md decorator-function} type.
*
* When registering a decorator function, it should be of this type.
*/
export type DecoratorDelegate = (
prog: TemplateDelegateFixed,
props: Record<string, any>,
container: Container,
options: any
) => any;
export interface HelpersHash {
[name: string]: Handlebars.HelperDelegate;
// -----------------------------------------------------------------------------
// INTERNAL TYPES
// -----------------------------------------------------------------------------
export type NodeType = typeof kHelper | typeof kAmbiguous | typeof kSimple;
type LookupProperty = <T = any>(parent: Record<string, any>, propertyName: string) => T;
export type NonBlockHelperOptions = Omit<HelperOptions, 'fn' | 'inverse'>;
export type AmbiguousHelperOptions = HelperOptions | NonBlockHelperOptions;
export type ProcessableStatementNode =
| hbs.AST.MustacheStatement
| hbs.AST.PartialStatement
| hbs.AST.SubExpression;
export type ProcessableBlockStatementNode = hbs.AST.BlockStatement | hbs.AST.PartialBlockStatement;
export type ProcessableNode = ProcessableStatementNode | ProcessableBlockStatementNode;
export type ProcessableNodeWithPathParts = ProcessableNode & { path: hbs.AST.PathExpression };
export type ProcessableNodeWithPathPartsOrLiteral = ProcessableNode & {
path: hbs.AST.PathExpression | hbs.AST.Literal;
};
export type HelpersHash = Record<string, HelperDelegateFixed>;
export type PartialsHash = Record<string, TemplateFixed>;
export type DecoratorsHash = Record<string, DecoratorDelegate>;
// Use the post-fix `Fixed` to allow us to acces it inside the 'handlebars' module declared above
type TemplateFixed = TemplateDelegateFixed | string;
export type { TemplateFixed as Template };
export interface DecoratorOptions extends Omit<HelperOptions, 'lookupProperties'> {
args?: any[];
}
export interface PartialsHash {
[name: string]: HandlebarsTemplateDelegate;
export interface VisitorHelper {
fn?: HelperDelegateFixed;
context: any[];
params: any[];
options: AmbiguousHelperOptions;
}
export interface DecoratorsHash {
[name: string]: DecoratorFunction;
export interface ResolvePartialOptions
extends Omit<Handlebars.ResolvePartialOptions, 'helpers' | 'partials' | 'decorators'> {
// The upstream `helpers` property is too loose and allows all functions.
helpers?: HelpersHash;
// The upstream `partials` property is incorrectly typed and doesn't allow
// partials to be strings.
partials?: PartialsHash;
// The upstream `decorators` property is too loose and allows all functions.
decorators?: DecoratorsHash;
}
export interface Container {
helpers: HelpersHash;
partials: PartialsHash;
decorators: DecoratorsHash;
strict: (obj: { [name: string]: any }, name: string, loc: hbs.AST.SourceLocation) => any;
strict: (obj: Record<string, any>, name: string, loc: hbs.AST.SourceLocation) => any;
lookupProperty: LookupProperty;
lambda: (current: any, context: any) => any;
data: (value: any, depth: number) => any;
hooks: {
helperMissing?: Handlebars.HelperDelegate;
blockHelperMissing?: Handlebars.HelperDelegate;
helperMissing?: HelperDelegateFixed;
blockHelperMissing?: HelperDelegateFixed;
};
}

View file

@ -18,13 +18,11 @@ import { moveHelperToHooks } from 'handlebars/dist/cjs/handlebars/helpers';
import type {
AmbiguousHelperOptions,
CompileOptions,
Container,
DecoratorFunction,
DecoratorDelegate,
DecoratorsHash,
ExtendedCompileOptions,
ExtendedRuntimeOptions,
Helper,
HelpersHash,
HelperOptions,
NodeType,
NonBlockHelperOptions,
ProcessableBlockStatementNode,
@ -32,6 +30,11 @@ import type {
ProcessableNodeWithPathParts,
ProcessableNodeWithPathPartsOrLiteral,
ProcessableStatementNode,
ResolvePartialOptions,
RuntimeOptions,
Template,
TemplateDelegate,
VisitorHelper,
} from './types';
import { kAmbiguous, kHelper, kSimple } from './symbols';
import {
@ -48,8 +51,8 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
private contexts: any[] = [];
private output: any[] = [];
private template?: string;
private compileOptions: ExtendedCompileOptions;
private runtimeOptions?: ExtendedRuntimeOptions;
private compileOptions: CompileOptions;
private runtimeOptions?: RuntimeOptions;
private blockParamNames: any[][] = [];
private blockParamValues: any[][] = [];
private ast?: hbs.AST.Program;
@ -61,7 +64,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
constructor(
env: typeof Handlebars,
input: string | hbs.AST.Program,
options: ExtendedCompileOptions = {}
options: CompileOptions = {}
) {
super();
@ -136,18 +139,15 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
};
}
render(context: any, options: ExtendedRuntimeOptions = {}): string {
render(context: any, options: RuntimeOptions = {}): string {
this.contexts = [context];
this.output = [];
this.runtimeOptions = { ...options };
this.container.helpers = {
...this.env.helpers,
...(options.helpers as HelpersHash),
};
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 as DecoratorsHash),
...options.decorators,
};
this.container.hooks = {};
this.processedRootDecorators = false;
@ -170,7 +170,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
// 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: Handlebars.TemplateDelegate = (_context) => {
const defaultMain: TemplateDelegate = (_context) => {
const prog = this.generateProgramFunction(this.ast!);
return prog(_context, this.runtimeOptions);
};
@ -296,7 +296,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
* 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: Handlebars.TemplateDelegate) {
private processDecorators(program: hbs.AST.Program, prog: TemplateDelegate) {
if (!this.processedDecoratorsForProgram.has(program)) {
this.processedDecoratorsForProgram.add(program);
const props = {};
@ -312,12 +312,12 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
private processDecorator(
decorator: hbs.AST.DecoratorBlock | hbs.AST.Decorator,
prog: Handlebars.TemplateDelegate,
prog: TemplateDelegate,
props: Record<string, any>
) {
const options = this.setupDecoratorOptions(decorator);
const result = this.container.lookupProperty<DecoratorFunction>(
const result = this.container.lookupProperty<DecoratorDelegate>(
this.container.decorators,
options.name
)(prog, props, this.container, options);
@ -499,10 +499,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
? this.resolveNodes(partial.name).join('')
: (partial.name as hbs.AST.PathExpression).original;
const options: AmbiguousHelperOptions & Handlebars.ResolvePartialOptions = this.setupParams(
partial,
name
);
const options: AmbiguousHelperOptions & ResolvePartialOptions = this.setupParams(partial, name);
options.helpers = this.container.helpers;
options.partials = this.container.partials;
options.decorators = this.container.decorators;
@ -516,7 +513,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
// Wrapper function to get access to currentPartialBlock from the closure
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
context: any,
wrapperOptions: { data?: Handlebars.HelperOptions['data'] } = {}
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.
@ -542,10 +539,17 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
context = Object.assign({}, context, options.hash);
}
const partialTemplate: Handlebars.Template | undefined =
const partialTemplate: Template | undefined =
this.container.partials[name] ??
partialBlock ??
Handlebars.VM.resolvePartial(undefined, undefined, options);
// 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`);
@ -619,7 +623,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
}
private setupHelper(node: ProcessableNode, helperName: string): Helper {
private setupHelper(node: ProcessableNode, helperName: string): VisitorHelper {
return {
fn: this.container.lookupProperty(this.container.helpers, helperName),
context: this.context,
@ -649,11 +653,11 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
return options;
}
private setupParams(node: ProcessableBlockStatementNode, name: string): Handlebars.HelperOptions;
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): AmbiguousHelperOptions {
const options = {
private setupParams(node: ProcessableNode, name: string) {
const options: AmbiguousHelperOptions = {
name,
hash: this.getHash(node),
data: this.runtimeOptions!.data,
@ -662,10 +666,10 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
};
if (isBlock(node)) {
(options as Handlebars.HelperOptions).fn = node.program
(options as HelperOptions).fn = node.program
? this.processDecorators(node.program, this.generateProgramFunction(node.program))
: noop;
(options as Handlebars.HelperOptions).inverse = node.inverse
(options as HelperOptions).inverse = node.inverse
? this.processDecorators(node.inverse, this.generateProgramFunction(node.inverse))
: noop;
}
@ -676,10 +680,7 @@ export class ElasticHandlebarsVisitor extends Handlebars.Visitor {
private generateProgramFunction(program: hbs.AST.Program) {
if (!program) return noop;
const prog: Handlebars.TemplateDelegate = (
nextContext: any,
runtimeOptions: ExtendedRuntimeOptions = {}
) => {
const prog: TemplateDelegate = (nextContext: any, runtimeOptions: RuntimeOptions = {}) => {
runtimeOptions = { ...runtimeOptions };
// inherit data in blockParams from parent program

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import Handlebars from '@kbn/handlebars';
import Handlebars, { type HelperOptions, type HelperDelegate } from '@kbn/handlebars';
import { encode } from '@kbn/rison';
import dateMath from '@kbn/datemath';
import moment, { Moment } from 'moment';
@ -18,9 +18,9 @@ const handlebars = Handlebars.create();
function createSerializationHelper(
fnName: string,
serializeFn: (value: unknown) => string
): Handlebars.HelperDelegate {
): HelperDelegate {
return (...args) => {
const { hash } = args.slice(-1)[0] as Handlebars.HelperOptions;
const { hash } = args.slice(-1)[0] as HelperOptions;
const hasHash = Object.keys(hash).length > 0;
const hasValues = args.length > 1;
if (hasHash && hasValues) {
@ -49,7 +49,7 @@ handlebars.registerHelper(
handlebars.registerHelper('date', (...args) => {
const values = args.slice(0, -1) as [string | Date, string | undefined];
const { hash } = args.slice(-1)[0] as Handlebars.HelperOptions;
const { hash } = args.slice(-1)[0] as HelperOptions;
// eslint-disable-next-line prefer-const
let [date, format] = values;
if (typeof date === 'undefined') throw new Error(`[date]: unknown variable`);

View file

@ -7,7 +7,12 @@
*/
import { encode } from '@kbn/rison';
import Handlebars, { type ExtendedCompileOptions, compileFnName } from '@kbn/handlebars';
import Handlebars, {
type CompileOptions,
type HelperOptions,
type HelperDelegate,
compileFnName,
} from '@kbn/handlebars';
import { i18n } from '@kbn/i18n';
import { emptyLabel } from '../../../../common/empty_label';
@ -16,9 +21,9 @@ const handlebars = Handlebars.create();
function createSerializationHelper(
fnName: string,
serializeFn: (value: unknown) => string
): Handlebars.HelperDelegate {
): HelperDelegate {
return (...args) => {
const { hash } = args.slice(-1)[0] as Handlebars.HelperOptions;
const { hash } = args.slice(-1)[0] as HelperOptions;
const hasHash = Object.keys(hash).length > 0;
const hasValues = args.length > 1;
if (hasHash && hasValues) {
@ -53,7 +58,7 @@ export function replaceVars(
str: string,
args: Record<string, unknown> = {},
vars: Record<string, unknown> = {},
compileOptions: Partial<ExtendedCompileOptions> = {}
compileOptions: Partial<CompileOptions> = {}
) {
try {
/** we need add '[]' for emptyLabel because this value contains special characters.