[@kbn/handlebars] Code cleanup (#147425)

This commit is contained in:
Thomas Watson 2022-12-19 18:24:26 +01:00 committed by GitHub
parent 794e721cc0
commit b00a2643cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 439 additions and 433 deletions

View file

@ -429,15 +429,19 @@
> beforeEach(() => {
> global.kbnHandlebarsEnv = Handlebars.create();
> });
410c319,323
410c319,327
< handlebarsEnv.registerDecorator('foo', function() {
---
> afterEach(() => {
> global.kbnHandlebarsEnv = null;
> });
>
> it('unregisters', () => {
> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property.
> kbnHandlebarsEnv!.decorators = {};
>
> kbnHandlebarsEnv!.registerDecorator('foo', function () {
414,416c327,329
414,416c331,333
< equals(!!handlebarsEnv.decorators.foo, true);
< handlebarsEnv.unregisterDecorator('foo');
< equals(handlebarsEnv.decorators.foo, undefined);
@ -445,14 +449,14 @@
> expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true);
> kbnHandlebarsEnv!.unregisterDecorator('foo');
> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined();
419,420c332,334
419,420c336,338
< it('allows multiple globals', function() {
< handlebarsEnv.decorators = {};
---
> it('allows multiple globals', () => {
> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property.
> kbnHandlebarsEnv!.decorators = {};
422,424c336,339
422,424c340,343
< handlebarsEnv.registerDecorator({
< foo: function() {},
< bar: function() {}
@ -461,7 +465,7 @@
> kbnHandlebarsEnv!.registerDecorator({
> foo() {},
> bar() {},
427,432c342,347
427,432c346,351
< equals(!!handlebarsEnv.decorators.foo, true);
< equals(!!handlebarsEnv.decorators.bar, true);
< handlebarsEnv.unregisterDecorator('foo');
@ -475,7 +479,7 @@
> kbnHandlebarsEnv!.unregisterDecorator('bar');
> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined();
> expect(kbnHandlebarsEnv!.decorators.bar).toBeUndefined();
435,445c350,356
435,445c354,360
< it('fails with multiple and args', function() {
< shouldThrow(
< function() {
@ -495,7 +499,7 @@
> {
> world() {
> return 'world!';
447,452c358,364
447,452c362,368
< {}
< );
< },

View file

@ -668,7 +668,10 @@
---
>
> afterEach(function () {
578,580c481,484
575a479,480
>
> global.kbnHandlebarsEnv = null;
578,580c483,486
< it('should call logger at default level', function() {
< var levelArg, logArg;
< handlebarsEnv.log = function(level, arg) {
@ -677,7 +680,7 @@
> let levelArg;
> let logArg;
> kbnHandlebarsEnv!.log = function (level, arg) {
585,590c489,491
585,590c491,493
< expectTemplate('{{log blah}}')
< .withInput({ blah: 'whee' })
< .withMessage('log should not display')
@ -688,7 +691,7 @@
> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(1).toEqual(levelArg);
> expect('whee').toEqual(logArg);
593,595c494,497
593,595c496,499
< it('should call logger at data level', function() {
< var levelArg, logArg;
< handlebarsEnv.log = function(level, arg) {
@ -697,20 +700,20 @@
> let levelArg;
> let logArg;
> kbnHandlebarsEnv!.log = function (level, arg) {
605,606c507,508
605,606c509,510
< equals('03', levelArg);
< equals('whee', logArg);
---
> expect('03').toEqual(levelArg);
> expect('whee').toEqual(logArg);
609,610c511,513
609,610c513,515
< it('should output to info', function() {
< var called;
---
> it('should output to info', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
612,616c515,521
612,616c517,523
< console.info = function(info) {
< equals('whee', info);
< called = true;
@ -724,7 +727,7 @@
> console.info = $info;
> console.log = $log;
> }
618,622c523,529
618,622c525,531
< console.log = function(log) {
< equals('whee', log);
< called = true;
@ -738,7 +741,7 @@
> console.info = $info;
> console.log = $log;
> }
625,628c532,533
625,628c534,535
< expectTemplate('{{log blah}}')
< .withInput({ blah: 'whee' })
< .toCompileTo('');
@ -746,14 +749,14 @@
---
> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(calls).toEqual(callsExpected);
631,632c536,538
631,632c538,540
< it('should log at data level', function() {
< var called;
---
> it('should log at data level', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
634,637c540,543
634,637c542,545
< console.error = function(log) {
< equals('whee', log);
< called = true;
@ -763,20 +766,20 @@
> expect('whee').toEqual(log);
> calls++;
> if (calls === callsExpected) console.error = $error;
645c551
645c553
< equals(true, called);
---
> expect(calls).toEqual(callsExpected);
648,649c554,556
648,649c556,558
< it('should handle missing logger', function() {
< var called = false;
---
> it('should handle missing logger', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
650a558
650a560
> // @ts-expect-error
652,655c560,563
652,655c562,565
< console.log = function(log) {
< equals('whee', log);
< called = true;
@ -786,18 +789,18 @@
> expect('whee').toEqual(log);
> calls++;
> if (calls === callsExpected) console.log = $log;
663c571
663c573
< equals(true, called);
---
> expect(calls).toEqual(callsExpected);
666,667c574,576
666,667c576,578
< it('should handle string log levels', function() {
< var called;
---
> it('should handle string log levels', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
669,671c578,580
669,671c580,582
< console.error = function(log) {
< equals('whee', log);
< called = true;
@ -805,26 +808,26 @@
> console.error = function (log) {
> expect('whee').toEqual(log);
> calls++;
679c588
679c590
< equals(true, called);
---
> expect(calls).toEqual(callsExpected);
681c590
681c592
< called = false;
---
> calls = 0;
688c597
688c599
< equals(true, called);
---
> expect(calls).toEqual(callsExpected);
691,692c600,602
691,692c602,604
< it('should handle hash log levels', function() {
< var called;
---
> it('should handle hash log levels [1]', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
694,696c604,606
694,696c606,608
< console.error = function(log) {
< equals('whee', log);
< called = true;
@ -832,7 +835,7 @@
> console.error = function (log) {
> expect('whee').toEqual(log);
> calls++;
699,702c609,610
699,702c611,612
< expectTemplate('{{log blah level="error"}}')
< .withInput({ blah: 'whee' })
< .toCompileTo('');
@ -840,13 +843,13 @@
---
> expectTemplate('{{log blah level="error"}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(calls).toEqual(callsExpected);
705,706c613,614
705,706c615,616
< it('should handle hash log levels', function() {
< var called = false;
---
> it('should handle hash log levels [2]', function () {
> let called = false;
708,711c616,623
708,711c618,625
< console.info = console.log = console.error = console.debug = function() {
< called = true;
< console.info = console.log = console.error = console.debug = $log;
@ -860,7 +863,7 @@
> called = true;
> console.info = console.log = console.error = console.debug = $log;
> };
713,716c625,626
713,716c627,628
< expectTemplate('{{log blah level="debug"}}')
< .withInput({ blah: 'whee' })
< .toCompileTo('');
@ -868,14 +871,14 @@
---
> expectTemplate('{{log blah level="debug"}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(false).toEqual(called);
719,720c629,631
719,720c631,633
< it('should pass multiple log arguments', function() {
< var called;
---
> it('should pass multiple log arguments', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
722,727c633,638
722,727c635,640
< console.info = console.log = function(log1, log2, log3) {
< equals('whee', log1);
< equals('foo', log2);
@ -889,7 +892,7 @@
> expect(1).toEqual(log3);
> calls++;
> if (calls === callsExpected) console.log = $log;
730,733c641,642
730,733c643,644
< expectTemplate('{{log blah "foo" 1}}')
< .withInput({ blah: 'whee' })
< .toCompileTo('');
@ -897,14 +900,14 @@
---
> expectTemplate('{{log blah "foo" 1}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(calls).toEqual(callsExpected);
736,737c645,647
736,737c647,649
< it('should pass zero log arguments', function() {
< var called;
---
> it('should pass zero log arguments', function () {
> let calls = 0;
> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
739,742c649,652
739,742c651,654
< console.info = console.log = function() {
< expect(arguments.length).to.equal(0);
< called = true;
@ -914,7 +917,7 @@
> expect(arguments.length).toEqual(0);
> calls++;
> if (calls === callsExpected) console.log = $log;
745,748c655,656
745,748c657,658
< expectTemplate('{{log}}')
< .withInput({ blah: 'whee' })
< .toCompileTo('');
@ -922,13 +925,13 @@
---
> expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo('');
> expect(calls).toEqual(callsExpected);
753,754c661,662
753,754c663,664
< describe('#lookup', function() {
< it('should lookup arbitrary content', function() {
---
> describe('#lookup', () => {
> it('should lookup arbitrary content', () => {
760c668
760c670
< it('should not fail on undefined value', function() {
---
> it('should not fail on undefined value', () => {

View file

@ -1,8 +1,14 @@
1,4c1,6
1,10c1,6
< describe('compiler', function() {
< if (!Handlebars.compile) {
< return;
< }
<
< describe('#equals', function() {
< function compile(string) {
< var ast = Handlebars.parse(string);
< return new Handlebars.Compiler().compile(ast, {});
< }
---
> /*
> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js),
@ -10,15 +16,7 @@
> * Elasticsearch B.V. licenses this file to you under the MIT License.
> * See `packages/kbn-handlebars/LICENSE` for more information.
> */
6,10c8
< describe('#equals', function() {
< function compile(string) {
< var ast = Handlebars.parse(string);
< return new Handlebars.Compiler().compile(ast, {});
< }
---
> import Handlebars from '../..';
12,60c10,13
12,60c8,9
< it('should treat as equal', function() {
< equal(compile('foo').equals(compile('foo')), true);
< equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
@ -69,11 +67,9 @@
< });
< });
---
> describe('compiler', () => {
> const compileFns = ['compile', 'compileAST'];
> if (process.env.AST) compileFns.splice(0, 1);
> else if (process.env.EVAL) compileFns.splice(1, 1);
62,78c15,17
> import Handlebars from '../..';
> import { forEachCompileFunctionName } from '../__jest__/test_bench';
62,78c11,13
< describe('#compile', function() {
< it('should fail with invalid input', function() {
< shouldThrow(
@ -92,10 +88,10 @@
< );
< });
---
> compileFns.forEach((compileName) => {
> // @ts-expect-error
> const compile = Handlebars[compileName];
80,92c19,24
> describe('compiler', () => {
> forEachCompileFunctionName((compileName) => {
> const compile = Handlebars[compileName].bind(Handlebars);
80,92c15,20
< it('should include the location in the error (row and column)', function() {
< try {
< Handlebars.compile(' \n {{#if}}\n{{/def}}')();
@ -116,7 +112,7 @@
> compile(null);
> }).toThrow(
> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed null`
94,102d25
94,102d21
< if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
< // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
< // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
@ -126,7 +122,7 @@
< equal(err.lineNumber, 2, 'Checking error row');
< }
< });
104,116c27,30
104,116c23,26
< it('should include the location as enumerable property', function() {
< try {
< Handlebars.compile(' \n {{#if}}\n{{/def}}')();
@ -145,7 +141,7 @@
> compile({});
> }).toThrow(
> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed [object Object]`
118,129c32
118,129c28
< }
< });
<
@ -160,7 +156,7 @@
< });
---
> });
131,133c34,48
131,133c30,44
< it('can pass through an empty string', function() {
< equal(Handlebars.compile('')(), '');
< });
@ -180,7 +176,7 @@
> expect(err.lineNumber).toEqual(2);
> }
> });
135,142c50,57
135,142c46,53
< it('should not modify the options.data property(GH-1327)', function() {
< var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
@ -198,7 +194,7 @@
> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true);
> }
> });
144,152c59,66
144,152c55,62
< it('should not modify the options.knownHelpers property(GH-1327)', function() {
< var options = { knownHelpers: {} };
< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
@ -217,7 +213,7 @@
> })({})
> ).toEqual('Hello');
> });
154,170c68,70
154,170c64,66
< describe('#precompile', function() {
< it('should fail with invalid input', function() {
< shouldThrow(
@ -239,7 +235,7 @@
> it('can pass through an empty string', () => {
> expect(compile('')({})).toEqual('');
> });
172,182c72,78
172,182c68,75
< it('can utilize AST instance', function() {
< equal(
< /return "Hello"/.test(
@ -253,13 +249,14 @@
< });
---
> it('should not modify the options.data property(GH-1327)', () => {
> const options = { data: [{ a: 'foo' }, { a: 'bar' }] };
> // The `data` property is supposed to be a boolean, but in this test we want to ignore that
> const options = { data: [{ a: 'foo' }, { a: 'bar' }] as unknown as boolean };
> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
> expect(JSON.stringify(options, null, 2)).toEqual(
> JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2)
> );
> });
184,185c80,86
184,185c77,83
< it('can pass through an empty string', function() {
< equal(/return ""/.test(Handlebars.precompile('')), true);
---

View file

@ -45,47 +45,50 @@
> for (const prop in options.hash) {
40d48
< .withMessage('Automatic data was triggered')
44c52
41a50,51
>
> global.kbnHandlebarsEnv = null;
44c54
< it('parameter data can be looked up via @foo', function() {
---
> it('parameter data can be looked up via @foo', () => {
47c55
47c57
< .withHelper('hello', function(noun) {
---
> .withHelper('hello', function (noun) {
50d57
50d59
< .withMessage('@foo as a parameter retrieves template data')
54c61
54c63
< it('hash values can be looked up via @foo', function() {
---
> it('hash values can be looked up via @foo', () => {
57c64
57c66
< .withHelper('hello', function(options) {
---
> .withHelper('hello', function (options) {
60d66
60d68
< .withMessage('@foo as a parameter retrieves template data')
64c70
64c72
< it('nested parameter data can be looked up via @foo.bar', function() {
---
> it('nested parameter data can be looked up via @foo.bar', () => {
67c73
67c75
< .withHelper('hello', function(noun) {
---
> .withHelper('hello', function (noun) {
70d75
70d77
< .withMessage('@foo as a parameter retrieves template data')
74c79
74c81
< it('nested parameter data does not fail with @world.bar', function() {
---
> it('nested parameter data does not fail with @world.bar', () => {
77c82
77c84
< .withHelper('hello', function(noun) {
---
> .withHelper('hello', function (noun) {
80d84
80d86
< .withMessage('@foo as a parameter retrieves template data')
84,87c88,89
84,87c90,91
< it('parameter data throws when using complex scope references', function() {
< expectTemplate(
< '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
@ -93,39 +96,39 @@
---
> it('parameter data throws when using complex scope references', () => {
> expectTemplate('{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}').toThrow(Error);
90c92
90c94
< it('data can be functions', function() {
---
> it('data can be functions', () => {
94c96
94c98
< hello: function() {
---
> hello() {
96,97c98,99
96,97c100,101
< }
< }
---
> },
> },
102c104
102c106
< it('data can be functions with params', function() {
---
> it('data can be functions with params', () => {
106c108
106c110
< hello: function(arg) {
---
> hello(arg: any) {
108,109c110,111
108,109c112,113
< }
< }
---
> },
> },
114c116
114c118
< it('data is inherited downstream', function() {
---
> it('data is inherited downstream', () => {
120,122c122,124
120,122c124,126
< .withHelper('let', function(options) {
< var frame = Handlebars.createFrame(options.data);
< for (var prop in options.hash) {
@ -133,9 +136,9 @@
> .withHelper('let', function (this: any, options) {
> const frame = Handlebars.createFrame(options.data);
> for (const prop in options.hash) {
130d131
130d133
< .withMessage('data variables are inherited downstream')
134,147c135
134,147c137
< it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
< expectTemplate('{{>myPartial}}')
< .withCompileOptions({ data: true })
@ -152,63 +155,63 @@
< it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
---
> it('passing in data to a compiled function that expects data - works with helpers and parameters', () => {
150c138
150c140
< .withHelper('hello', function(noun, options) {
---
> .withHelper('hello', function (this: any, noun, options) {
155d142
155d144
< .withMessage('Data output by helper')
159c146
159c148
< it('passing in data to a compiled function that expects data - works with block helpers', function() {
---
> it('passing in data to a compiled function that expects data - works with block helpers', () => {
162c149
162c151
< data: true
---
> data: true,
164c151
164c153
< .withHelper('hello', function(options) {
---
> .withHelper('hello', function (this: any, options) {
167c154
167c156
< .withHelper('world', function(options) {
---
> .withHelper('world', function (this: any, options) {
172d158
172d160
< .withMessage('Data output by helper')
176c162
176c164
< it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
---
> it('passing in data to a compiled function that expects data - works with block helpers that use ..', () => {
179c165
179c167
< .withHelper('hello', function(options) {
---
> .withHelper('hello', function (options) {
182c168
182c170
< .withHelper('world', function(thing, options) {
---
> .withHelper('world', function (this: any, thing, options) {
187d172
187d174
< .withMessage('Data output by helper')
191c176
191c178
< it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
---
> it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', () => {
194c179
194c181
< .withHelper('hello', function(options) {
---
> .withHelper('hello', function (options) {
197c182
197c184
< .withHelper('world', function(thing, options) {
---
> .withHelper('world', function (this: any, thing, options) {
202d186
202d188
< .withMessage('Data output by helper')
206c190
206c192
< it('you can override inherited data when invoking a helper', function() {
---
> it('you can override inherited data when invoking a helper', () => {
209,213c193,194
209,213c195,196
< .withHelper('hello', function(options) {
< return options.fn(
< { exclaim: '?', zomg: 'world' },
@ -217,55 +220,55 @@
---
> .withHelper('hello', function (options) {
> return options.fn({ exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } });
215c196
215c198
< .withHelper('world', function(thing, options) {
---
> .withHelper('world', function (this: any, thing, options) {
220d200
220d202
< .withMessage('Overriden data output by helper')
224c204
224c206
< it('you can override inherited data when invoking a helper with depth', function() {
---
> it('you can override inherited data when invoking a helper with depth', () => {
227c207
227c209
< .withHelper('hello', function(options) {
---
> .withHelper('hello', function (options) {
230c210
230c212
< .withHelper('world', function(thing, options) {
---
> .withHelper('world', function (this: any, thing, options) {
235d214
235d216
< .withMessage('Overriden data output by helper')
239,240c218,219
239,240c220,221
< describe('@root', function() {
< it('the root context can be looked up via @root', function() {
---
> describe('@root', () => {
> it('the root context can be looked up via @root', () => {
246,248c225
246,248c227
< expectTemplate('{{@root.foo}}')
< .withInput({ foo: 'hello' })
< .toCompileTo('hello');
---
> expectTemplate('{{@root.foo}}').withInput({ foo: 'hello' }).toCompileTo('hello');
251c228
251c230
< it('passed root values take priority', function() {
---
> it('passed root values take priority', () => {
259,260c236,237
259,260c238,239
< describe('nesting', function() {
< it('the root context can be looked up via @root', function() {
---
> describe('nesting', () => {
> it('the root context can be looked up via @root', () => {
265,266c242,243
265,266c244,245
< .withHelper('helper', function(options) {
< var frame = Handlebars.createFrame(options.data);
---
> .withHelper('helper', function (this: any, options) {
> const frame = Handlebars.createFrame(options.data);
272,273c249,250
272,273c251,252
< depth: 0
< }
---

File diff suppressed because it is too large Load diff

View file

@ -5,17 +5,18 @@ A custom version of the handlebars package which, to improve security, does not
## Limitations
- Only the following compile options are supported:
- `data`
- `knownHelpers`
- `knownHelpersOnly`
- `noEscape`
- `strict`
- `assumeObjects`
- `noEscape`
- `data`
- Only the following runtime options are supported:
- `helpers`
- `blockParams`
- `data`
- `helpers`
- `decorators` (not documented in the official Handlebars [runtime options documentation](https://handlebarsjs.com/api-reference/runtime-options.html))
- `blockParams` (not documented in the official Handlebars [runtime options documentation](https://handlebarsjs.com/api-reference/runtime-options.html))
The [Inline partials](https://handlebarsjs.com/guide/partials.html#inline-partials) handlebars template feature is currently not supported by `@kbn/handlebars`.

View file

@ -1,19 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Handlebars.compileAST invalid template 1`] = `
"Parse error on line 1:
{{value
--^
Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'"
`;
exports[`Handlebars.compileAST invalid template 2`] = `
"Parse error on line 1:
{{value
--^
Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'"
`;
exports[`Handlebars.create 1`] = `
HandlebarsEnvironment {
"AST": Object {
@ -103,7 +89,3 @@ HandlebarsEnvironment {
"template": [Function],
}
`;
exports[`blocks decorators registration should not be able to call decorators unregistered using the \`unregisterDecorator\` function 1`] = `"lookupProperty(...) is not a function"`;
exports[`blocks decorators registration should not be able to call decorators unregistered using the \`unregisterDecorator\` function 2`] = `"decoratorFn is not a function"`;

View file

@ -11,7 +11,7 @@
*/
import Handlebars from '.';
import { expectTemplate } from './src/__jest__/test_bench';
import { expectTemplate, forEachCompileFunctionName } from './src/__jest__/test_bench';
it('Handlebars.create', () => {
expect(Handlebars.create()).toMatchSnapshot();
@ -35,7 +35,10 @@ describe('Handlebars.compileAST', () => {
});
it('invalid template', () => {
expectTemplate('{{value').withInput({ value: 42 }).toThrowErrorMatchingSnapshot();
expectTemplate('{{value').withInput({ value: 42 }).toThrow(`Parse error on line 1:
{{value
--^
Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'`);
});
if (!process.env.EVAL) {
@ -108,33 +111,27 @@ describe('blocks', () => {
expect(calls).toEqual(callsExpected);
});
it('should call decorator again if render function is called again', () => {
const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
forEachCompileFunctionName((compileName) => {
it(`should call decorator again if render function is called again for #${compileName}`, () => {
global.kbnHandlebarsEnv = Handlebars.create();
global.kbnHandlebarsEnv = Handlebars.create();
kbnHandlebarsEnv!.registerDecorator('decorator', () => {
calls++;
});
kbnHandlebarsEnv!.registerDecorator('decorator', () => {
calls++;
const compile = kbnHandlebarsEnv![compileName].bind(kbnHandlebarsEnv);
const render = compile('{{*decorator}}');
let calls = 0;
expect(render({})).toEqual('');
expect(calls).toEqual(1);
calls = 0;
expect(render({})).toEqual('');
expect(calls).toEqual(1);
global.kbnHandlebarsEnv = null;
});
let renderAST;
let renderEval;
if (process.env.AST || !process.env.EVAL) {
renderAST = kbnHandlebarsEnv!.compileAST('{{*decorator}}');
}
if (process.env.EVAL || !process.env.AST) {
renderEval = kbnHandlebarsEnv!.compile('{{*decorator}}');
}
let calls = 0;
if (renderAST) expect(renderAST({})).toEqual('');
if (renderEval) expect(renderEval({})).toEqual('');
expect(calls).toEqual(callsExpected);
calls = 0;
if (renderAST) expect(renderAST({})).toEqual('');
if (renderEval) expect(renderEval({})).toEqual('');
expect(calls).toEqual(callsExpected);
});
it('should pass expected options to nested decorator', () => {
@ -207,6 +204,10 @@ describe('blocks', () => {
global.kbnHandlebarsEnv = Handlebars.create();
});
afterEach(() => {
global.kbnHandlebarsEnv = null;
});
it('should be able to call decorators registered using the `registerDecorator` function', () => {
let calls = 0;
const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
@ -228,7 +229,7 @@ describe('blocks', () => {
kbnHandlebarsEnv!.unregisterDecorator('decorator');
expectTemplate('{{*decorator}}').toThrowErrorMatchingSnapshot();
expectTemplate('{{*decorator}}').toThrow('lookupProperty(...) is not a function');
expect(calls).toEqual(0);
});
});

View file

@ -50,7 +50,7 @@ export const compileFnName: 'compile' | 'compileAST' = allowUnsafeEval() ? 'comp
*/
export type ExtendedCompileOptions = Pick<
CompileOptions,
'knownHelpers' | 'knownHelpersOnly' | 'strict' | 'assumeObjects' | 'noEscape' | 'data'
'data' | 'knownHelpers' | 'knownHelpersOnly' | 'noEscape' | 'strict' | 'assumeObjects'
>;
/**
@ -61,7 +61,7 @@ export type ExtendedCompileOptions = Pick<
*/
export type ExtendedRuntimeOptions = Pick<
RuntimeOptions,
'helpers' | 'blockParams' | 'data' | 'decorators'
'data' | 'helpers' | 'decorators' | 'blockParams'
>;
/**
@ -77,14 +77,14 @@ export type DecoratorFunction = (
options: any
) => any;
export interface DecoratorsHash {
[name: string]: DecoratorFunction;
}
export interface HelpersHash {
[name: string]: Handlebars.HelperDelegate;
}
export interface DecoratorsHash {
[name: string]: DecoratorFunction;
}
/**
* Normally this namespace isn't used directly. It's required to be present by
* TypeScript when calling the `Handlebars.create()` function.
@ -298,6 +298,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
Program(program: hbs.AST.Program) {
this.blockParamNames.unshift(program.blockParams);
// Run any decorators that might exist on the root
this.processDecorators(program, this.generateProgramFunction(program));
this.processedRootDecorators = true;
@ -314,13 +315,13 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
// This space intentionally left blank: We want to override the Visitor class implementation
// of `DecoratorBlock`, but since we handle decorators separately before traversing the
// nodes, we just want to make this a no-op.
// of this method, but since we handle decorators separately before traversing the nodes, we
// just want to make this a no-op.
DecoratorBlock(decorator: hbs.AST.DecoratorBlock) {}
// This space intentionally left blank: We want to override the Visitor class implementation
// of `DecoratorBlock`, but since we handle decorators separately before traversing the
// nodes, we just want to make this a no-op.
// of this method, but since we handle decorators separately before traversing the nodes, we
// just want to make this a no-op.
Decorator(decorator: hbs.AST.Decorator) {}
SubExpression(sexpr: hbs.AST.SubExpression) {
@ -393,20 +394,23 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
) {
// TypeScript: The types indicate that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too.
const name = (decorator.path as hbs.AST.PathExpression).original;
const decoratorFn = this.container.lookupProperty<DecoratorFunction>(
this.container.decorators,
name
);
const props = {};
// TypeScript: Because `decorator` can be of type `hbs.AST.Decorator`, TS indicates that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too.
const options = this.setupParams(decorator as hbs.AST.DecoratorBlock, name);
// @ts-expect-error: Property 'lookupProperty' does not exist on type 'HelperOptions'
delete options.lookupProperty; // There's really no tests/documentation on this, but to match the upstream codebase we'll remove `lookupProperty` from the decorator context
Object.assign(decoratorFn(prog, props, this.container, options) || prog, props);
const result = this.container.lookupProperty<DecoratorFunction>(
this.container.decorators,
name
)(prog, props, this.container, options);
Object.assign(result || prog, props);
}
private processStatementOrExpression(node: ProcessableNode) {
// Calling `transformLiteralToPath` has side-effects!
// It converts a node from type `ProcessableNode` to `ProcessableNodeWithPathParts`
transformLiteralToPath(node);
switch (this.classifyNode(node as ProcessableNodeWithPathParts)) {
@ -533,6 +537,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
private invokeKnownHelper(node: ProcessableNodeWithPathParts) {
const name = node.path.parts[0];
const helper = this.setupHelper(node, name);
// TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
const result = helper.fn.apply(helper.context, helper.params);
this.output.push(result);
}
@ -558,6 +563,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
}
// TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
const result = helper.fn.apply(helper.context, helper.params);
this.output.push(result);
@ -573,8 +579,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
} else {
if (
// @ts-expect-error: The `escaped` property is only on MustacheStatement nodes
node.escaped === false ||
(node as hbs.AST.MustacheStatement).escaped === false ||
this.compileOptions.noEscape === true ||
typeof invokeResult !== 'string'
) {
@ -679,8 +684,9 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
nextContext: any,
runtimeOptions: ExtendedRuntimeOptions = {}
) => {
// inherit data in blockParams from parent program
runtimeOptions = Object.assign({}, runtimeOptions);
// inherit data in blockParams from parent program
runtimeOptions.data = runtimeOptions.data || this.runtimeOptions!.data;
if (runtimeOptions.blockParams) {
runtimeOptions.blockParams = runtimeOptions.blockParams.concat(

View file

@ -10,6 +10,11 @@ import Handlebars, {
type ExtendedRuntimeOptions,
} from '../..';
type CompileFns = 'compile' | 'compileAST';
const compileFns: CompileFns[] = ['compile', 'compileAST'];
if (process.env.AST) compileFns.splice(0, 1);
else if (process.env.EVAL) compileFns.splice(1, 1);
declare global {
var kbnHandlebarsEnv: typeof Handlebars | null; // eslint-disable-line no-var
}
@ -24,12 +29,18 @@ export function expectTemplate(template: string, options?: TestOptions) {
return new HandlebarsTestBench(template, options);
}
export function forEachCompileFunctionName(
cb: (compileName: CompileFns, index: number, array: CompileFns[]) => void
) {
compileFns.forEach(cb);
}
class HandlebarsTestBench {
private template: string;
private options: TestOptions;
private compileOptions?: ExtendedCompileOptions;
private runtimeOptions?: ExtendedRuntimeOptions;
private helpers: { [key: string]: Handlebars.HelperDelegate | undefined } = {};
private helpers: { [name: string]: Handlebars.HelperDelegate | undefined } = {};
private decorators: DecoratorsHash = {};
private input: any = {};
@ -58,7 +69,7 @@ class HandlebarsTestBench {
return this;
}
withHelpers(helperFunctions: { [key: string]: Handlebars.HelperDelegate }) {
withHelpers(helperFunctions: { [name: string]: Handlebars.HelperDelegate }) {
for (const [name, helper] of Object.entries(helperFunctions)) {
this.withHelper(name, helper);
}
@ -108,12 +119,6 @@ class HandlebarsTestBench {
}
}
toThrowErrorMatchingSnapshot() {
const { renderEval, renderAST } = this.compile();
expect(() => renderEval(this.input)).toThrowErrorMatchingSnapshot();
expect(() => renderAST(this.input)).toThrowErrorMatchingSnapshot();
}
private compileAndExecute() {
if (process.env.EVAL) {
return {
@ -159,15 +164,6 @@ class HandlebarsTestBench {
return renderAST(this.input, runtimeOptions);
}
private compile() {
const handlebarsEnv = getHandlebarsEnv();
return {
renderEval: this.compileEval(handlebarsEnv),
renderAST: this.compileAST(handlebarsEnv),
};
}
private compileEval(handlebarsEnv = getHandlebarsEnv()) {
this.execBeforeEach();
return handlebarsEnv.compile(this.template, this.compileOptions);

View file

@ -316,6 +316,10 @@ describe('blocks', () => {
global.kbnHandlebarsEnv = Handlebars.create();
});
afterEach(() => {
global.kbnHandlebarsEnv = null;
});
it('unregisters', () => {
// @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property.
kbnHandlebarsEnv!.decorators = {};

View file

@ -476,6 +476,8 @@ describe('builtin helpers', () => {
console.log = $log;
console.info = $info;
console.error = $error;
global.kbnHandlebarsEnv = null;
});
it('should call logger at default level', function () {

View file

@ -6,15 +6,11 @@
*/
import Handlebars from '../..';
import { forEachCompileFunctionName } from '../__jest__/test_bench';
describe('compiler', () => {
const compileFns = ['compile', 'compileAST'];
if (process.env.AST) compileFns.splice(0, 1);
else if (process.env.EVAL) compileFns.splice(1, 1);
compileFns.forEach((compileName) => {
// @ts-expect-error
const compile = Handlebars[compileName];
forEachCompileFunctionName((compileName) => {
const compile = Handlebars[compileName].bind(Handlebars);
describe(`#${compileName}`, () => {
it('should fail with invalid input', () => {
@ -70,7 +66,8 @@ describe('compiler', () => {
});
it('should not modify the options.data property(GH-1327)', () => {
const options = { data: [{ a: 'foo' }, { a: 'bar' }] };
// The `data` property is supposed to be a boolean, but in this test we want to ignore that
const options = { data: [{ a: 'foo' }, { a: 'bar' }] as unknown as boolean };
compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
expect(JSON.stringify(options, null, 2)).toEqual(
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2)

View file

@ -47,6 +47,8 @@ describe('data', () => {
.withInput({ foo: true })
.withHelpers(helpers)
.toCompileTo('Hello world');
global.kbnHandlebarsEnv = null;
});
it('parameter data can be looked up via @foo', () => {

View file

@ -12,6 +12,10 @@ beforeEach(() => {
global.kbnHandlebarsEnv = Handlebars.create();
});
afterEach(() => {
global.kbnHandlebarsEnv = null;
});
describe('helpers', () => {
it('helper with complex lookup$', () => {
expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}')