mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[@kbn/handlebars] Refactor types (#150520)
This commit is contained in:
parent
50b83014a3
commit
77ed48a75a
14 changed files with 304 additions and 216 deletions
|
@ -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()}")`;
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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' })
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue