diff options
Diffstat (limited to 'dev/lib')
33 files changed, 1 insertions, 6818 deletions
| diff --git a/dev/lib/handlebars.js b/dev/lib/handlebars.js index c99a3b13..82fa4552 100644 --- a/dev/lib/handlebars.js +++ b/dev/lib/handlebars.js @@ -14,5 +14,5 @@   * You should have received a copy of the GNU General Public License   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -export {Handlebars} from './handlebars/src/handlebars.js'; +export {Handlebars} from 'yomitan-handlebars/src/handlebars.js'; diff --git a/dev/lib/handlebars/LICENSE b/dev/lib/handlebars/LICENSE deleted file mode 100644 index 5d971a17..00000000 --- a/dev/lib/handlebars/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Elasticsearch BV -Copyright (c) Copyright (C) 2011-2019 by Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at the following locations: - - https://github.com/handlebars-lang/handlebars.js - - https://github.com/elastic/kibana/tree/main/packages/kbn-handlebars diff --git a/dev/lib/handlebars/README.md b/dev/lib/handlebars/README.md deleted file mode 100644 index 1ec14ef4..00000000 --- a/dev/lib/handlebars/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# @kbn/handlebars - -A custom version of the handlebars package which, to improve security, does not use `eval` or `new Function`. This means that templates can't be compiled into JavaScript functions in advance and hence, rendering the templates is a lot slower. - -## Limitations - -- Only the following compile options are supported: - -  - `data` -  - `knownHelpers` -  - `knownHelpersOnly` -  - `noEscape` -  - `strict` -  - `assumeObjects` -  - `preventIndent` -  - `explicitPartialContext` - -- Only the following runtime options are supported: -  - `data` -  - `helpers` -  - `partials` -  - `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)) - -## Implementation differences - -The standard `handlebars` implementation: - -1. When given a template string, e.g. `Hello {{x}}`, return a "render" function which takes an "input" object, e.g. `{ x: 'World' }`. -1. The first time the "render" function is called the following happens: -   1. Turn the template string into an Abstract Syntax Tree (AST). -   1. Convert the AST into a hyper optimized JavaScript function which takes the input object as an argument. -   1. Call the generate JavaScript function with the given "input" object to produce and return the final output string (`Hello World`). -1. Subsequent calls to the "render" function will re-use the already generated JavaScript function. - -The custom `@kbn/handlebars` implementation: - -1. When given a template string, e.g. `Hello {{x}}`, return a "render" function which takes an "input" object, e.g. `{ x: 'World' }`. -1. The first time the "render" function is called the following happens: -   1. Turn the template string into an Abstract Syntax Tree (AST). -   1. Process the AST with the given "input" object to produce and return the final output string (`Hello World`). -1. Subsequent calls to the "render" function will re-use the already generated AST. - -_Note: Not parsing of the template string until the first call to the "render" function is deliberate as it mimics the original `handlebars` implementation. This means that any errors that occur due to an invalid template string will not be thrown until the first call to the "render" function._ - -## Technical details - -The `handlebars` library exposes the API for both [generating the AST](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md#ast) and walking it by implementing the [Visitor API](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md#ast-visitor). We can leverage that to our advantage and create our own "render" function, which internally calls this API to generate the AST and then the API to walk the AST. - -The `@kbn/handlebars` implementation of the `Visitor` class implements all the necessary methods called by the parent `Visitor` code when instructed to walk the AST. They all start with an upppercase letter, e.g. `MustacheStatement` or `SubExpression`. We call this class `ElasticHandlebarsVisitor`. - -To parse the template string to an AST representation, we call `Handlebars.parse(templateString)`, which returns an AST object. - -The AST object contains a bunch of nodes, one for each element of the template string, all arranged in a tree-like structure. The root of the AST object is a node of type `Program`. This is a special node, which we do not need to worry about, but each of its direct children has a type named like the method which will be called when the walking algorithm reaches that node, e.g. `ContentStatement` or `BlockStatement`. These are the methods that our `Visitor` implementation implements. - -To instruct our `ElasticHandlebarsVisitor` class to start walking the AST object, we call the `accept()` method inherited from the parent `Visitor` class with the main AST object. The `Visitor` will walk each node in turn that is directly attached to the root `Program` node. For each node it traverses, it will call the matching method in our `ElasticHandlebarsVisitor` class. - -To instruct the `Visitor` code to traverse any child nodes of a given node, our implementation needs to manually call `accept(childNode)`, `acceptArray(arrayOfChildNodes)`, `acceptKey(node, childKeyName)`, or `acceptRequired(node, childKeyName)` from within any of the "node" methods, otherwise the child nodes are ignored. - -### State - -We keep state internally in the `ElasticHandlebarsVisitor` object using the following private properties: - -- `contexts`: An array (stack) of `context` objects. In a simple template this array will always only contain a single element: The main `context` object. In more complicated scenarios, new `context` objects will be pushed and popped to and from the `contexts` stack as needed. -- `output`: An array containing the "rendered" output of each node (normally just one element per node). In the most simple template, this is simply joined together into a the final output string after the AST has been traversed. In more complicated templates, we use this array temporarily to collect parameters to give to helper functions (see the `getParams` function). - -## Testing - -The tests for `@kbn/handlebars` are integrated into the regular test suite of Kibana and are all jest tests. To run them all, simply do: - -```sh -node scripts/jest packages/kbn-handlebars -``` - -By default, each test will run both the original `handlebars` code and the modified `@kbn/handlebars` code to compare if the output of the two are identical. To isolate a test run to just one or the other, you can use the following environment variables: - -- `EVAL=1` - Set to only run the original `handlebars` implementation that uses `eval`. -- `AST=1` - Set to only run the modified `@kbn/handlebars` implementation that doesn't use `eval`. - -## Development - -Some of the tests have been copied from the upstream `handlebars` project and modified to fit our use-case, test-suite, and coding conventions. They are all located under the `packages/kbn-handlebars/src/spec` directory. To check if any of the copied files have received updates upstream that we might want to include in our copies, you can run the following script: - -```sh -./packages/kbn-handlebars/scripts/check_for_upstream_updates.sh -``` - -_Note: This will look for changes in the `4.x` branch of the `handlebars.js` repo only. Changes in the `master` branch are ignored._ - -Once all updates have been manually merged with our versions of the files, run the following script to "lock" us into the new updates: - -```sh -./packages/kbn-handlebars/scripts/update_upstream_git_hash.sh -``` - -This will update file `packages/kbn-handlebars/src/spec/.upstream_git_hash`. Make sure to commit changes to this file as well. - -## Debugging - -### Print AST - -To output the generated AST object structure in a somewhat readable form, use the following script: - -```sh -./packages/kbn-handlebars/scripts/print_ast.js -``` - -Example: - -```sh -./packages/kbn-handlebars/scripts/print_ast.js '{{value}}' -``` - -Output: - -```js -{ -  type: 'Program', -  body: [ -    { -      type: 'MustacheStatement', -      path: { -        type: 'PathExpression', -        data: false, -        depth: 0, -        parts: [ 'value' ], -        original: 'value' -      }, -      params: [], -      hash: undefined, -      escaped: true -    } -  ] -} -``` - -By default certain properties will be hidden in the output. -For more control over the output, check out the options by running the script without any arguments. - -### Print generated code - -It's possible to see the generated JavaScript code that `handlebars` create for a given template using the following command line tool: - -```sh -./node_modules/handlebars/print-script <template> [options] -``` - -Options: - -- `-v`: Enable verbose mode. - -Example: - -```sh -./node_modules/handlebars/print-script '{{value}}' -v -``` - -You can pretty print just the generated code using this command: - -```sh -./node_modules/handlebars/print-script '{{value}}' | \ -  node -e 'process.stdin.on(`data`, c => console.log(`(${eval(`(${c})`).code})`))' | \ -  npx prettier --write --stdin-filepath template.js | \ -  npx cli-highlight -l javascript -``` diff --git a/dev/lib/handlebars/__snapshots__/index.test.ts.snap b/dev/lib/handlebars/__snapshots__/index.test.ts.snap deleted file mode 100644 index b9a8c27e..00000000 --- a/dev/lib/handlebars/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,91 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Handlebars.create 1`] = ` -HandlebarsEnvironment { -  "AST": Object { -    "helpers": Object { -      "helperExpression": [Function], -      "scopedId": [Function], -      "simpleId": [Function], -    }, -  }, -  "COMPILER_REVISION": 8, -  "Compiler": [Function], -  "Exception": [Function], -  "HandlebarsEnvironment": [Function], -  "JavaScriptCompiler": [Function], -  "LAST_COMPATIBLE_COMPILER_REVISION": 7, -  "Parser": Object { -    "yy": Object {}, -  }, -  "REVISION_CHANGES": Object { -    "1": "<= 1.0.rc.2", -    "2": "== 1.0.0-rc.3", -    "3": "== 1.0.0-rc.4", -    "4": "== 1.x.x", -    "5": "== 2.0.0-alpha.x", -    "6": ">= 2.0.0-beta.1", -    "7": ">= 4.0.0 <4.3.0", -    "8": ">= 4.3.0", -  }, -  "SafeString": [Function], -  "Utils": Object { -    "__esModule": true, -    "appendContextPath": [Function], -    "blockParams": [Function], -    "createFrame": [Function], -    "escapeExpression": [Function], -    "extend": [Function], -    "indexOf": [Function], -    "isArray": [Function], -    "isEmpty": [Function], -    "isFunction": [Function], -    "toString": [Function], -  }, -  "VERSION": "4.7.7", -  "VM": Object { -    "__esModule": true, -    "checkRevision": [Function], -    "invokePartial": [Function], -    "noop": [Function], -    "resolvePartial": [Function], -    "template": [Function], -    "wrapProgram": [Function], -  }, -  "__esModule": true, -  "compile": [Function], -  "compileAST": [Function], -  "createFrame": [Function], -  "decorators": Object { -    "inline": [Function], -  }, -  "escapeExpression": [Function], -  "helpers": Object { -    "blockHelperMissing": [Function], -    "each": [Function], -    "helperMissing": [Function], -    "if": [Function], -    "log": [Function], -    "lookup": [Function], -    "unless": [Function], -    "with": [Function], -  }, -  "log": [Function], -  "logger": Object { -    "level": "info", -    "log": [Function], -    "lookupLevel": [Function], -    "methodMap": Array [ -      "debug", -      "info", -      "warn", -      "error", -    ], -  }, -  "parse": [Function], -  "parseWithoutProcessing": [Function], -  "partials": Object {}, -  "precompile": [Function], -  "template": [Function], -} -`; diff --git a/dev/lib/handlebars/index.test.ts b/dev/lib/handlebars/index.test.ts deleted file mode 100644 index ed607db1..00000000 --- a/dev/lib/handlebars/index.test.ts +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -/** - * ABOUT THIS FILE: - * - * This file is for tests not copied from the upstream handlebars project, but - * tests that we feel are needed in order to fully cover our use-cases. - */ - -import Handlebars from '.'; -import type { HelperOptions, TemplateDelegate } from './src/types'; -import { expectTemplate, forEachCompileFunctionName } from './src/__jest__/test_bench'; - -it('Handlebars.create', () => { -  expect(Handlebars.create()).toMatchSnapshot(); -}); - -describe('Handlebars.compileAST', () => { -  describe('compiler options', () => { -    it('noEscape', () => { -      expectTemplate('{{value}}').withInput({ value: '<foo>' }).toCompileTo('<foo>'); - -      expectTemplate('{{value}}') -        .withCompileOptions({ noEscape: false }) -        .withInput({ value: '<foo>' }) -        .toCompileTo('<foo>'); - -      expectTemplate('{{value}}') -        .withCompileOptions({ noEscape: true }) -        .withInput({ value: '<foo>' }) -        .toCompileTo('<foo>'); -    }); -  }); - -  it('invalid template', () => { -    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) { -    it('reassign', () => { -      const fn = Handlebars.compileAST; -      expect(fn('{{value}}')({ value: 42 })).toEqual('42'); -    }); -  } -}); - -// Extra "helpers" tests -describe('helpers', () => { -  it('Only provide options.fn/inverse to block helpers', () => { -    function toHaveProperties(...args: any[]) { -      toHaveProperties.calls++; -      const options = args[args.length - 1]; -      expect(options).toHaveProperty('fn'); -      expect(options).toHaveProperty('inverse'); -      return 42; -    } -    toHaveProperties.calls = 0; - -    function toNotHaveProperties(...args: any[]) { -      toNotHaveProperties.calls++; -      const options = args[args.length - 1]; -      expect(options).not.toHaveProperty('fn'); -      expect(options).not.toHaveProperty('inverse'); -      return 42; -    } -    toNotHaveProperties.calls = 0; - -    const nonBlockTemplates = ['{{foo}}', '{{foo 1 2}}']; -    const blockTemplates = ['{{#foo}}42{{/foo}}', '{{#foo 1 2}}42{{/foo}}']; - -    for (const template of nonBlockTemplates) { -      expectTemplate(template) -        .withInput({ -          foo: toNotHaveProperties, -        }) -        .toCompileTo('42'); - -      expectTemplate(template).withHelper('foo', toNotHaveProperties).toCompileTo('42'); -    } - -    for (const template of blockTemplates) { -      expectTemplate(template) -        .withInput({ -          foo: toHaveProperties, -        }) -        .toCompileTo('42'); - -      expectTemplate(template).withHelper('foo', toHaveProperties).toCompileTo('42'); -    } - -    const factor = process.env.AST || process.env.EVAL ? 1 : 2; -    expect(toNotHaveProperties.calls).toEqual(nonBlockTemplates.length * 2 * factor); -    expect(toHaveProperties.calls).toEqual(blockTemplates.length * 2 * factor); -  }); - -  it('should pass expected "this" to helper functions (without input)', () => { -    expectTemplate('{{hello "world" 12 true false}}') -      .withHelper('hello', function (this: any, ...args: any[]) { -        expect(this).toMatchInlineSnapshot(`Object {}`); -      }) -      .toCompileTo(''); -  }); - -  it('should pass expected "this" to helper functions (with input)', () => { -    expectTemplate('{{hello "world" 12 true false}}') -      .withHelper('hello', function (this: any, ...args: any[]) { -        expect(this).toMatchInlineSnapshot(` -          Object { -            "people": Array [ -              Object { -                "id": 1, -                "name": "Alan", -              }, -              Object { -                "id": 2, -                "name": "Yehuda", -              }, -            ], -          } -        `); -      }) -      .withInput({ -        people: [ -          { name: 'Alan', id: 1 }, -          { name: 'Yehuda', id: 2 }, -        ], -      }) -      .toCompileTo(''); -  }); - -  it('should pass expected "this" and arguments to helper functions (non-block helper)', () => { -    expectTemplate('{{hello "world" 12 true false}}') -      .withHelper('hello', function (this: any, ...args: any[]) { -        expect(args).toMatchInlineSnapshot(` -          Array [ -            "world", -            12, -            true, -            false, -            Object { -              "data": Object { -                "root": Object { -                  "people": Array [ -                    Object { -                      "id": 1, -                      "name": "Alan", -                    }, -                    Object { -                      "id": 2, -                      "name": "Yehuda", -                    }, -                  ], -                }, -              }, -              "hash": Object {}, -              "loc": Object { -                "end": Object { -                  "column": 31, -                  "line": 1, -                }, -                "start": Object { -                  "column": 0, -                  "line": 1, -                }, -              }, -              "lookupProperty": [Function], -              "name": "hello", -            }, -          ] -        `); -      }) -      .withInput({ -        people: [ -          { name: 'Alan', id: 1 }, -          { name: 'Yehuda', id: 2 }, -        ], -      }) -      .toCompileTo(''); -  }); - -  it('should pass expected "this" and arguments to helper functions (block helper)', () => { -    expectTemplate('{{#hello "world" 12 true false}}{{/hello}}') -      .withHelper('hello', function (this: any, ...args: any[]) { -        expect(args).toMatchInlineSnapshot(` -          Array [ -            "world", -            12, -            true, -            false, -            Object { -              "data": Object { -                "root": Object { -                  "people": Array [ -                    Object { -                      "id": 1, -                      "name": "Alan", -                    }, -                    Object { -                      "id": 2, -                      "name": "Yehuda", -                    }, -                  ], -                }, -              }, -              "fn": [Function], -              "hash": Object {}, -              "inverse": [Function], -              "loc": Object { -                "end": Object { -                  "column": 42, -                  "line": 1, -                }, -                "start": Object { -                  "column": 0, -                  "line": 1, -                }, -              }, -              "lookupProperty": [Function], -              "name": "hello", -            }, -          ] -        `); -      }) -      .withInput({ -        people: [ -          { name: 'Alan', id: 1 }, -          { name: 'Yehuda', id: 2 }, -        ], -      }) -      .toCompileTo(''); -  }); -}); - -// Extra "blocks" tests -describe('blocks', () => { -  describe('decorators', () => { -    it('should only call decorator once', () => { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -      expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') -        .withHelper('helper', () => {}) -        .withDecorator('decorator', () => { -          calls++; -        }) -        .toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    forEachCompileFunctionName((compileName) => { -      it(`should call decorator again if render function is called again for #${compileName}`, () => { -        global.kbnHandlebarsEnv = Handlebars.create(); - -        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; -      }); -    }); - -    it('should pass expected options to nested decorator', () => { -      expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') -        .withHelper('helper', () => {}) -        .withDecorator('decorator', function (fn, props, container, options) { -          expect(options).toMatchInlineSnapshot(` -            Object { -              "args": Array [ -                "bar", -              ], -              "data": Object { -                "root": Object { -                  "foo": "bar", -                }, -              }, -              "hash": Object {}, -              "loc": Object { -                "end": Object { -                  "column": 29, -                  "line": 1, -                }, -                "start": Object { -                  "column": 11, -                  "line": 1, -                }, -              }, -              "name": "decorator", -            } -          `); -        }) -        .withInput({ foo: 'bar' }) -        .toCompileTo(''); -    }); - -    it('should pass expected options to root decorator with no args', () => { -      expectTemplate('{{*decorator}}') -        .withDecorator('decorator', function (fn, props, container, options) { -          expect(options).toMatchInlineSnapshot(` -            Object { -              "args": Array [], -              "data": Object { -                "root": Object { -                  "foo": "bar", -                }, -              }, -              "hash": Object {}, -              "loc": Object { -                "end": Object { -                  "column": 14, -                  "line": 1, -                }, -                "start": Object { -                  "column": 0, -                  "line": 1, -                }, -              }, -              "name": "decorator", -            } -          `); -        }) -        .withInput({ foo: 'bar' }) -        .toCompileTo(''); -    }); - -    it('should pass expected options to root decorator with one arg', () => { -      expectTemplate('{{*decorator foo}}') -        .withDecorator('decorator', function (fn, props, container, options) { -          expect(options).toMatchInlineSnapshot(` -            Object { -              "args": Array [ -                undefined, -              ], -              "data": Object { -                "root": Object { -                  "foo": "bar", -                }, -              }, -              "hash": Object {}, -              "loc": Object { -                "end": Object { -                  "column": 18, -                  "line": 1, -                }, -                "start": Object { -                  "column": 0, -                  "line": 1, -                }, -              }, -              "name": "decorator", -            } -          `); -        }) -        .withInput({ foo: 'bar' }) -        .toCompileTo(''); -    }); - -    describe('return values', () => { -      for (const [desc, template, result] of [ -        ['non-block', '{{*decorator}}cont{{*decorator}}ent', 'content'], -        ['block', '{{#*decorator}}con{{/decorator}}tent', 'tent'], -      ]) { -        describe(desc, () => { -          const falsy = [undefined, null, false, 0, '']; -          const truthy = [true, 42, 'foo', {}]; - -          // Falsy return values from decorators are simply ignored and the -          // execution falls back to default behavior which is to render the -          // other parts of the template. -          for (const value of falsy) { -            it(`falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { -              expectTemplate(template) -                .withDecorator('decorator', () => value) -                .toCompileTo(result); -            }); -          } - -          // Truthy return values from decorators are expected to be functions -          // and the program will attempt to call them. We expect an error to -          // be thrown in this case. -          for (const value of truthy) { -            it(`non-falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { -              expectTemplate(template) -                .withDecorator('decorator', () => value) -                .toThrow('is not a function'); -            }); -          } - -          // If the decorator return value is a custom function, its return -          // value will be the final content of the template. -          for (const value of [...falsy, ...truthy]) { -            it(`function returning ${typeof value}: ${JSON.stringify(value)}`, () => { -              expectTemplate(template) -                .withDecorator('decorator', () => () => value) -                .toCompileTo(value as string); -            }); -          } -        }); -      } -    }); - -    describe('custom return function should be called with expected arguments and its return value should be rendered in the template', () => { -      it('root decorator', () => { -        expectTemplate('{{*decorator}}world') -          .withInput({ me: 'my' }) -          .withDecorator( -            'decorator', -            (fn): TemplateDelegate => -              (context, options) => { -                expect(context).toMatchInlineSnapshot(` -              Object { -                "me": "my", -              } -            `); -                expect(options).toMatchInlineSnapshot(` -              Object { -                "decorators": Object { -                  "decorator": [Function], -                }, -                "helpers": Object {}, -                "partials": Object {}, -              } -            `); -                return `hello ${context.me} ${fn()}!`; -              } -          ) -          .toCompileTo('hello my world!'); -      }); - -      it('decorator nested inside of array-helper', () => { -        expectTemplate('{{#arr}}{{*decorator}}world{{/arr}}') -          .withInput({ arr: ['my'] }) -          .withDecorator( -            'decorator', -            (fn): TemplateDelegate => -              (context, options) => { -                expect(context).toMatchInlineSnapshot(`"my"`); -                expect(options).toMatchInlineSnapshot(` -              Object { -                "blockParams": Array [ -                  "my", -                  0, -                ], -                "data": Object { -                  "_parent": Object { -                    "root": Object { -                      "arr": Array [ -                        "my", -                      ], -                    }, -                  }, -                  "first": true, -                  "index": 0, -                  "key": 0, -                  "last": true, -                  "root": Object { -                    "arr": Array [ -                      "my", -                    ], -                  }, -                }, -              } -            `); -                return `hello ${context} ${fn()}!`; -              } -          ) -          .toCompileTo('hello my world!'); -      }); - -      it('decorator nested inside of custom helper', () => { -        expectTemplate('{{#helper}}{{*decorator}}world{{/helper}}') -          .withHelper('helper', function (options: HelperOptions) { -            return options.fn('my', { foo: 'bar' } as any); -          }) -          .withDecorator( -            'decorator', -            (fn): TemplateDelegate => -              (context, options) => { -                expect(context).toMatchInlineSnapshot(`"my"`); -                expect(options).toMatchInlineSnapshot(` -              Object { -                "foo": "bar", -              } -            `); -                return `hello ${context} ${fn()}!`; -              } -          ) -          .toCompileTo('hello my world!'); -      }); -    }); - -    it('should call multiple decorators in the same program body in the expected order and get the expected output', () => { -      let decoratorCall = 0; -      let progCall = 0; -      expectTemplate('{{*decorator}}con{{*decorator}}tent', { -        beforeRender() { -          // ensure the counters are reset between EVAL/AST render calls -          decoratorCall = 0; -          progCall = 0; -        }, -      }) -        .withInput({ -          decoratorCall: 0, -          progCall: 0, -        }) -        .withDecorator('decorator', (fn) => { -          const decoratorCallOrder = ++decoratorCall; -          const ret: TemplateDelegate = () => { -            const progCallOrder = ++progCall; -            return `(decorator: ${decoratorCallOrder}, prog: ${progCallOrder}, fn: "${fn()}")`; -          }; -          return ret; -        }) -        .toCompileTo('(decorator: 2, prog: 1, fn: "(decorator: 1, prog: 2, fn: "content")")'); -    }); - -    describe('registration', () => { -      beforeEach(() => { -        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; - -        kbnHandlebarsEnv!.registerDecorator('decorator', () => { -          calls++; -        }); - -        expectTemplate('{{*decorator}}').toCompileTo(''); -        expect(calls).toEqual(callsExpected); -      }); - -      it('should not be able to call decorators unregistered using the `unregisterDecorator` function', () => { -        let calls = 0; - -        kbnHandlebarsEnv!.registerDecorator('decorator', () => { -          calls++; -        }); - -        kbnHandlebarsEnv!.unregisterDecorator('decorator'); - -        expectTemplate('{{*decorator}}').toThrow('lookupProperty(...) is not a function'); -        expect(calls).toEqual(0); -      }); -    }); -  }); -}); diff --git a/dev/lib/handlebars/index.ts b/dev/lib/handlebars/index.ts deleted file mode 100644 index 16030445..00000000 --- a/dev/lib/handlebars/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import { Handlebars } from './src/handlebars'; -import { allowUnsafeEval } from './src/utils'; - -// The handlebars module uses `export =`, so it can't be re-exported using `export *`. -// However, because of Babel, we're not allowed to use `export =` ourselves. -// So we have to resort to using `exports default` even though eslint doesn't like it. -// -// eslint-disable-next-line import/no-default-export -globalThis.Handlebars = Handlebars; - -/** - * If the `unsafe-eval` CSP is set, this string constant will be `compile`, - * otherwise `compileAST`. - * - * This can be used to call the more optimized `compile` function in - * environments that support it, or fall back to `compileAST` on environments - * that don't. - */ -globalThis.handlebarsCompileFnName = allowUnsafeEval() ? 'compile' : 'compileAST'; - -export type { -  CompileOptions, -  RuntimeOptions, -  HelperDelegate, -  TemplateDelegate, -  DecoratorDelegate, -  HelperOptions, -} from './src/types'; diff --git a/dev/lib/handlebars/jest.config.js b/dev/lib/handlebars/jest.config.js deleted file mode 100644 index feb9f905..00000000 --- a/dev/lib/handlebars/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -module.exports = { -  preset: '@kbn/test', -  rootDir: '../..', -  roots: ['<rootDir>/packages/kbn-handlebars'], -}; diff --git a/dev/lib/handlebars/kibana.jsonc b/dev/lib/handlebars/kibana.jsonc deleted file mode 100644 index 59b3c28d..00000000 --- a/dev/lib/handlebars/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ -  "type": "shared-common", -  "id": "@kbn/handlebars", -  "owner": "@elastic/kibana-security" -} diff --git a/dev/lib/handlebars/package.json b/dev/lib/handlebars/package.json deleted file mode 100644 index 46ca823a..00000000 --- a/dev/lib/handlebars/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ -  "name": "@kbn/handlebars", -  "version": "1.0.0", -  "private": true, -  "license": "MIT" -}
\ No newline at end of file diff --git a/dev/lib/handlebars/scripts/check_for_upstream_updates.sh b/dev/lib/handlebars/scripts/check_for_upstream_updates.sh deleted file mode 100755 index 73f7376a..00000000 --- a/dev/lib/handlebars/scripts/check_for_upstream_updates.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -set -e - -TMP=.tmp-handlebars -HASH_FILE=packages/kbn-handlebars/src/spec/.upstream_git_hash - -function cleanup { -  rm -fr $TMP -} - -trap cleanup EXIT - -rm -fr $TMP -mkdir $TMP - -echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP - -echo "Looking for updates..." -hash=$(git -C $TMP rev-parse HEAD) -expected_hash=$(cat $HASH_FILE) - -if [ "$hash" = "$expected_hash" ]; then -  echo "You're all up to date :)" -else -  echo -  echo "New changes has been committed to the '4.x' branch in the upstream git repository" -  echo -  echo "To resolve this issue, do the following:" -  echo -  echo "  1. Investigate the commits in the '4.x' branch of the upstream git repository." -  echo "     If files inside the 'spec' folder has been updated, sync those updates with" -  echo "     our local versions of these files (located in" -  echo "     'packages/kbn-handlebars/src/spec')." -  echo -  echo "     https://github.com/handlebars-lang/handlebars.js/compare/$hash...4.x" -  echo -  echo "  2. Execute the following script and commit the updated '$HASH_FILE'" -  echo "     file including any changes you made to our own spec files." -  echo -  echo "     ./packages/kbn-handlebars/scripts/update_upstream_git_hash.sh" -  echo -  exit 1 -fi diff --git a/dev/lib/handlebars/scripts/print_ast.js b/dev/lib/handlebars/scripts/print_ast.js deleted file mode 100755 index b97fb5a6..00000000 --- a/dev/lib/handlebars/scripts/print_ast.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env node -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ -'use strict'; // eslint-disable-line strict - -const { relative } = require('path'); -const { inspect } = require('util'); - -const { parse } = require('handlebars'); -const argv = require('minimist')(process.argv.slice(2)); - -const DEFAULT_FILTER = 'loc,strip,openStrip,inverseStrip,closeStrip'; - -const filter = argv['show-all'] ? [''] : (argv.filter || DEFAULT_FILTER).split(','); -const hideEmpty = argv['hide-empty'] || false; -const template = argv._[0]; - -if (template === undefined) { -  const script = relative(process.cwd(), process.argv[1]); -  console.log(`Usage: ${script} [options] <template>`); -  console.log(); -  console.log('Options:'); -  console.log('  --filter=...  A comma separated list of keys to filter from the output.'); -  console.log(`                Default: ${DEFAULT_FILTER}`); -  console.log('  --hide-empty  Do not display empty properties.'); -  console.log('  --show-all    Do not filter out any properties. Equivalent to --filter="".'); -  console.log(); -  console.log('Example:'); -  console.log(`  ${script} --hide-empty -- 'hello {{name}}'`); -  console.log(); -  process.exit(1); -} - -console.log(inspect(reduce(parse(template, filter)), { colors: true, depth: null })); - -function reduce(ast) { -  if (Array.isArray(ast)) { -    for (let i = 0; i < ast.length; i++) { -      ast[i] = reduce(ast[i]); -    } -  } else { -    for (const k of filter) { -      delete ast[k]; -    } - -    if (hideEmpty) { -      for (const [k, v] of Object.entries(ast)) { -        if (v === undefined || v === null || (Array.isArray(v) && v.length === 0)) { -          delete ast[k]; -        } -      } -    } - -    for (const [k, v] of Object.entries(ast)) { -      if (typeof v === 'object' && v !== null) { -        ast[k] = reduce(v); -      } -    } -  } - -  return ast; -} diff --git a/dev/lib/handlebars/scripts/update_upstream_git_hash.sh b/dev/lib/handlebars/scripts/update_upstream_git_hash.sh deleted file mode 100755 index 52cc39e0..00000000 --- a/dev/lib/handlebars/scripts/update_upstream_git_hash.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -e - -TMP=.tmp-handlebars -HASH_FILE=packages/kbn-handlebars/src/spec/.upstream_git_hash - -function cleanup { -  rm -fr $TMP -} - -trap cleanup EXIT - -rm -fr $TMP -mkdir $TMP - -echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP - -echo "Updating hash file..." -git -C $TMP rev-parse HEAD | tr -d '\n' > $HASH_FILE -git add $HASH_FILE - -echo "Done! - Don't forget to commit any changes to $HASH_FILE" diff --git a/dev/lib/handlebars/src/__jest__/test_bench.ts b/dev/lib/handlebars/src/__jest__/test_bench.ts deleted file mode 100644 index d17f7f12..00000000 --- a/dev/lib/handlebars/src/__jest__/test_bench.ts +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -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']; -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 -} - -global.kbnHandlebarsEnv = null; - -interface TestOptions { -  beforeEach?: Function; -  beforeRender?: Function; -} - -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?: CompileOptions; -  private runtimeOptions?: RuntimeOptions; -  private helpers: HelpersHash = {}; -  private partials: PartialsHash = {}; -  private decorators: DecoratorsHash = {}; -  private input: any = {}; - -  constructor(template: string, options: TestOptions = {}) { -    this.template = template; -    this.options = options; -  } - -  withCompileOptions(compileOptions?: CompileOptions) { -    this.compileOptions = compileOptions; -    return this; -  } - -  withRuntimeOptions(runtimeOptions?: RuntimeOptions) { -    this.runtimeOptions = runtimeOptions; -    return this; -  } - -  withInput(input: any) { -    this.input = input; -    return this; -  } - -  withHelper<F extends HelperDelegate>(name: string, helper: F) { -    this.helpers[name] = helper; -    return this; -  } - -  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: Template) { -    this.partials[name] = partial; -    return this; -  } - -  withPartials(partials: Record<string, Template>) { -    for (const [name, partial] of Object.entries(partials)) { -      this.withPartial(name, partial); -    } -    return this; -  } - -  withDecorator(name: string, decoratorFunction: DecoratorDelegate) { -    this.decorators[name] = decoratorFunction; -    return this; -  } - -  withDecorators(decoratorFunctions: Record<string, DecoratorDelegate>) { -    for (const [name, decoratorFunction] of Object.entries(decoratorFunctions)) { -      this.withDecorator(name, decoratorFunction); -    } -    return this; -  } - -  toCompileTo(outputExpected: string) { -    const { outputEval, outputAST } = this.compileAndExecute(); -    if (process.env.EVAL) { -      expect(outputEval).toEqual(outputExpected); -    } else if (process.env.AST) { -      expect(outputAST).toEqual(outputExpected); -    } else { -      expect(outputAST).toEqual(outputExpected); -      expect(outputAST).toEqual(outputEval); -    } -  } - -  toThrow(error?: string | RegExp | jest.Constructable | Error | undefined) { -    if (process.env.EVAL) { -      expect(() => { -        this.compileAndExecuteEval(); -      }).toThrowError(error); -    } else if (process.env.AST) { -      expect(() => { -        this.compileAndExecuteAST(); -      }).toThrowError(error); -    } else { -      expect(() => { -        this.compileAndExecuteEval(); -      }).toThrowError(error); -      expect(() => { -        this.compileAndExecuteAST(); -      }).toThrowError(error); -    } -  } - -  private compileAndExecute() { -    if (process.env.EVAL) { -      return { -        outputEval: this.compileAndExecuteEval(), -      }; -    } else if (process.env.AST) { -      return { -        outputAST: this.compileAndExecuteAST(), -      }; -    } else { -      return { -        outputEval: this.compileAndExecuteEval(), -        outputAST: this.compileAndExecuteAST(), -      }; -    } -  } - -  private compileAndExecuteEval() { -    const renderEval = this.compileEval(); - -    const runtimeOptions: RuntimeOptions = { -      helpers: this.helpers, -      partials: this.partials, -      decorators: this.decorators, -      ...this.runtimeOptions, -    }; - -    this.execBeforeRender(); - -    return renderEval(this.input, runtimeOptions); -  } - -  private compileAndExecuteAST() { -    const renderAST = this.compileAST(); - -    const runtimeOptions: RuntimeOptions = { -      helpers: this.helpers, -      partials: this.partials, -      decorators: this.decorators, -      ...this.runtimeOptions, -    }; - -    this.execBeforeRender(); - -    return renderAST(this.input, runtimeOptions); -  } - -  private compileEval(handlebarsEnv = getHandlebarsEnv()) { -    this.execBeforeEach(); -    return handlebarsEnv.compile(this.template, this.compileOptions); -  } - -  private compileAST(handlebarsEnv = getHandlebarsEnv()) { -    this.execBeforeEach(); -    return handlebarsEnv.compileAST(this.template, this.compileOptions); -  } - -  private execBeforeRender() { -    this.options.beforeRender?.(); -  } - -  private execBeforeEach() { -    if (this.options.beforeEach) { -      this.options.beforeEach(); -    } -  } -} - -function getHandlebarsEnv() { -  return kbnHandlebarsEnv || Handlebars.create(); -} diff --git a/dev/lib/handlebars/src/handlebars.ts b/dev/lib/handlebars/src/handlebars.ts deleted file mode 100644 index 358d1b73..00000000 --- a/dev/lib/handlebars/src/handlebars.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -// The handlebars module uses `export =`, so we should technically use `import Handlebars = require('handlebars')`, but Babel will not allow this: -// https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require -import Handlebars = require('handlebars'); - -import type { CompileOptions, RuntimeOptions, TemplateDelegate } from './types'; -import { ElasticHandlebarsVisitor } from './visitor'; - -const originalCreate = Handlebars.create; - -export { Handlebars }; - -/** - * Creates an isolated Handlebars environment. - * - * Each environment has its own helpers. - * This is only necessary for use cases that demand distinct helpers. - * Most use cases can use the root Handlebars environment directly. - * - * @returns A sandboxed/scoped version of the @kbn/handlebars module - */ -Handlebars.create = function (): typeof Handlebars { -  const SandboxedHandlebars = originalCreate.call(Handlebars) as typeof Handlebars; -  // When creating new Handlebars environments, ensure the custom compileAST function is present in the new environment as well -  SandboxedHandlebars.compileAST = Handlebars.compileAST; -  return SandboxedHandlebars; -}; - -Handlebars.compileAST = function ( -  input: string | hbs.AST.Program, -  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}` -    ); -  } - -  // If `Handlebars.compileAST` is reassigned, `this` will be undefined. -  const visitor = new ElasticHandlebarsVisitor(this ?? Handlebars, input, options); - -  return (context: any, runtimeOptions?: RuntimeOptions) => visitor.render(context, runtimeOptions); -}; diff --git a/dev/lib/handlebars/src/spec/.upstream_git_hash b/dev/lib/handlebars/src/spec/.upstream_git_hash deleted file mode 100644 index 5a6b1831..00000000 --- a/dev/lib/handlebars/src/spec/.upstream_git_hash +++ /dev/null @@ -1 +0,0 @@ -c65c6cce3f626e4896a9d59250f0908be695adae
\ No newline at end of file diff --git a/dev/lib/handlebars/src/spec/index.basic.test.ts b/dev/lib/handlebars/src/spec/index.basic.test.ts deleted file mode 100644 index 6acf3ae9..00000000 --- a/dev/lib/handlebars/src/spec/index.basic.test.ts +++ /dev/null @@ -1,481 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('basic context', () => { -  it('most basic', () => { -    expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); -  }); - -  it('escaping', () => { -    expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); -    expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); -    expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); -    expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); -    expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); -  }); - -  it('compiling with a basic context', () => { -    expectTemplate('Goodbye\n{{cruel}}\n{{world}}!') -      .withInput({ -        cruel: 'cruel', -        world: 'world', -      }) -      .toCompileTo('Goodbye\ncruel\nworld!'); -  }); - -  it('compiling with a string context', () => { -    expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3'); -  }); - -  it('compiling with an undefined context', () => { -    expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') -      .withInput(undefined) -      .toCompileTo('Goodbye\n\n!'); - -    expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}') -      .withInput(undefined) -      .toCompileTo('Goodbye'); -  }); - -  it('comments', () => { -    expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!') -      .withInput({ -        cruel: 'cruel', -        world: 'world', -      }) -      .toCompileTo('Goodbye\ncruel\nworld!'); - -    expectTemplate('    {{~! comment ~}}      blah').toCompileTo('blah'); -    expectTemplate('    {{~!-- long-comment --~}}      blah').toCompileTo('blah'); -    expectTemplate('    {{! comment ~}}      blah').toCompileTo('    blah'); -    expectTemplate('    {{!-- long-comment --~}}      blah').toCompileTo('    blah'); -    expectTemplate('    {{~! comment}}      blah').toCompileTo('      blah'); -    expectTemplate('    {{~!-- long-comment --}}      blah').toCompileTo('      blah'); -  }); - -  it('boolean', () => { -    const string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; -    expectTemplate(string) -      .withInput({ -        goodbye: true, -        world: 'world', -      }) -      .toCompileTo('GOODBYE cruel world!'); - -    expectTemplate(string) -      .withInput({ -        goodbye: false, -        world: 'world', -      }) -      .toCompileTo('cruel world!'); -  }); - -  it('zeros', () => { -    expectTemplate('num1: {{num1}}, num2: {{num2}}') -      .withInput({ -        num1: 42, -        num2: 0, -      }) -      .toCompileTo('num1: 42, num2: 0'); - -    expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0'); - -    expectTemplate('num: {{num1/num2}}') -      .withInput({ num1: { num2: 0 } }) -      .toCompileTo('num: 0'); -  }); - -  it('false', () => { -    /* eslint-disable no-new-wrappers */ -    expectTemplate('val1: {{val1}}, val2: {{val2}}') -      .withInput({ -        val1: false, -        val2: new Boolean(false), -      }) -      .toCompileTo('val1: false, val2: false'); - -    expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false'); - -    expectTemplate('val: {{val1/val2}}') -      .withInput({ val1: { val2: false } }) -      .toCompileTo('val: false'); - -    expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}') -      .withInput({ -        val1: false, -        val2: new Boolean(false), -      }) -      .toCompileTo('val1: false, val2: false'); - -    expectTemplate('val: {{{val1/val2}}}') -      .withInput({ val1: { val2: false } }) -      .toCompileTo('val: false'); -    /* eslint-enable */ -  }); - -  it('should handle undefined and null', () => { -    expectTemplate('{{awesome undefined null}}') -      .withInput({ -        awesome(_undefined: any, _null: any, options: any) { -          return (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options; -        }, -      }) -      .toCompileTo('true true object'); - -    expectTemplate('{{undefined}}') -      .withInput({ -        undefined() { -          return 'undefined!'; -        }, -      }) -      .toCompileTo('undefined!'); - -    expectTemplate('{{null}}') -      .withInput({ -        null() { -          return 'null!'; -        }, -      }) -      .toCompileTo('null!'); -  }); - -  it('newlines', () => { -    expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); -    expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); -  }); - -  it('escaping text', () => { -    expectTemplate("Awesome's").toCompileTo("Awesome's"); -    expectTemplate('Awesome\\').toCompileTo('Awesome\\'); -    expectTemplate('Awesome\\\\ foo').toCompileTo('Awesome\\\\ foo'); -    expectTemplate('Awesome {{foo}}').withInput({ foo: '\\' }).toCompileTo('Awesome \\'); -    expectTemplate(" ' ' ").toCompileTo(" ' ' "); -  }); - -  it('escaping expressions', () => { -    expectTemplate('{{{awesome}}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); - -    expectTemplate('{{&awesome}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); - -    expectTemplate('{{awesome}}') -      .withInput({ awesome: '&"\'`\\<>' }) -      .toCompileTo('&"'`\\<>'); - -    expectTemplate('{{awesome}}') -      .withInput({ awesome: 'Escaped, <b> looks like: <b>' }) -      .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); -  }); - -  it("functions returning safestrings shouldn't be escaped", () => { -    expectTemplate('{{awesome}}') -      .withInput({ -        awesome() { -          return new Handlebars.SafeString("&'\\<>"); -        }, -      }) -      .toCompileTo("&'\\<>"); -  }); - -  it('functions', () => { -    expectTemplate('{{awesome}}') -      .withInput({ -        awesome() { -          return 'Awesome'; -        }, -      }) -      .toCompileTo('Awesome'); - -    expectTemplate('{{awesome}}') -      .withInput({ -        awesome() { -          return this.more; -        }, -        more: 'More awesome', -      }) -      .toCompileTo('More awesome'); -  }); - -  it('functions with context argument', () => { -    expectTemplate('{{awesome frank}}') -      .withInput({ -        awesome(context: any) { -          return context; -        }, -        frank: 'Frank', -      }) -      .toCompileTo('Frank'); -  }); - -  it('pathed functions with context argument', () => { -    expectTemplate('{{bar.awesome frank}}') -      .withInput({ -        bar: { -          awesome(context: any) { -            return context; -          }, -        }, -        frank: 'Frank', -      }) -      .toCompileTo('Frank'); -  }); - -  it('depthed functions with context argument', () => { -    expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') -      .withInput({ -        awesome(context: any) { -          return context; -        }, -        frank: 'Frank', -      }) -      .toCompileTo('Frank'); -  }); - -  it('block functions with context argument', () => { -    expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') -      .withInput({ -        awesome(context: any, options: any) { -          return options.fn(context); -        }, -      }) -      .toCompileTo('inner 1'); -  }); - -  it('depthed block functions with context argument', () => { -    expectTemplate('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}') -      .withInput({ -        value: true, -        awesome(context: any, options: any) { -          return options.fn(context); -        }, -      }) -      .toCompileTo('inner 1'); -  }); - -  it('block functions without context argument', () => { -    expectTemplate('{{#awesome}}inner{{/awesome}}') -      .withInput({ -        awesome(options: any) { -          return options.fn(this); -        }, -      }) -      .toCompileTo('inner'); -  }); - -  it('pathed block functions without context argument', () => { -    expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') -      .withInput({ -        foo: { -          awesome() { -            return this; -          }, -        }, -      }) -      .toCompileTo('inner'); -  }); - -  it('depthed block functions without context argument', () => { -    expectTemplate('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}') -      .withInput({ -        value: true, -        awesome() { -          return this; -        }, -      }) -      .toCompileTo('inner'); -  }); - -  it('paths with hyphens', () => { -    expectTemplate('{{foo-bar}}').withInput({ 'foo-bar': 'baz' }).toCompileTo('baz'); - -    expectTemplate('{{foo.foo-bar}}') -      .withInput({ foo: { 'foo-bar': 'baz' } }) -      .toCompileTo('baz'); - -    expectTemplate('{{foo/foo-bar}}') -      .withInput({ foo: { 'foo-bar': 'baz' } }) -      .toCompileTo('baz'); -  }); - -  it('nested paths', () => { -    expectTemplate('Goodbye {{alan/expression}} world!') -      .withInput({ alan: { expression: 'beautiful' } }) -      .toCompileTo('Goodbye beautiful world!'); -  }); - -  it('nested paths with empty string value', () => { -    expectTemplate('Goodbye {{alan/expression}} world!') -      .withInput({ alan: { expression: '' } }) -      .toCompileTo('Goodbye  world!'); -  }); - -  it('literal paths', () => { -    expectTemplate('Goodbye {{[@alan]/expression}} world!') -      .withInput({ '@alan': { expression: 'beautiful' } }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate('Goodbye {{[foo bar]/expression}} world!') -      .withInput({ 'foo bar': { expression: 'beautiful' } }) -      .toCompileTo('Goodbye beautiful world!'); -  }); - -  it('literal references', () => { -    expectTemplate('Goodbye {{[foo bar]}} world!') -      .withInput({ 'foo bar': 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate('Goodbye {{"foo bar"}} world!') -      .withInput({ 'foo bar': 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate("Goodbye {{'foo bar'}} world!") -      .withInput({ 'foo bar': 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate('Goodbye {{"foo[bar"}} world!') -      .withInput({ 'foo[bar': 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate('Goodbye {{"foo\'bar"}} world!') -      .withInput({ "foo'bar": 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); - -    expectTemplate("Goodbye {{'foo\"bar'}} world!") -      .withInput({ 'foo"bar': 'beautiful' }) -      .toCompileTo('Goodbye beautiful world!'); -  }); - -  it("that current context path ({{.}}) doesn't hit helpers", () => { -    expectTemplate('test: {{.}}') -      .withInput(null) -      // @ts-expect-error Setting the helper to a string instead of a function doesn't make sense normally, but here it doesn't matter -      .withHelpers({ helper: 'awesome' }) -      .toCompileTo('test: '); -  }); - -  it('complex but empty paths', () => { -    expectTemplate('{{person/name}}') -      .withInput({ person: { name: null } }) -      .toCompileTo(''); - -    expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo(''); -  }); - -  it('this keyword in paths', () => { -    expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') -      .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) -      .toCompileTo('goodbyeGoodbyeGOODBYE'); - -    expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') -      .withInput({ -        hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], -      }) -      .toCompileTo('helloHelloHELLO'); -  }); - -  it('this keyword nested inside path', () => { -    expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( -      'Invalid path: text/this - 1:13' -    ); - -    expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar'); - -    expectTemplate('{{text/[this]}}') -      .withInput({ text: { this: 'bar' } }) -      .toCompileTo('bar'); -  }); - -  it('this keyword in helpers', () => { -    const helpers = { -      foo(value: any) { -        return 'bar ' + value; -      }, -    }; - -    expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}') -      .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) -      .withHelpers(helpers) -      .toCompileTo('bar goodbyebar Goodbyebar GOODBYE'); - -    expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}') -      .withInput({ -        hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], -      }) -      .withHelpers(helpers) -      .toCompileTo('bar hellobar Hellobar HELLO'); -  }); - -  it('this keyword nested inside helpers param', () => { -    expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( -      'Invalid path: text/this - 1:17' -    ); - -    expectTemplate('{{foo [this]}}') -      .withInput({ -        foo(value: any) { -          return value; -        }, -        this: 'bar', -      }) -      .toCompileTo('bar'); - -    expectTemplate('{{foo text/[this]}}') -      .withInput({ -        foo(value: any) { -          return value; -        }, -        text: { this: 'bar' }, -      }) -      .toCompileTo('bar'); -  }); - -  it('pass string literals', () => { -    expectTemplate('{{"foo"}}').toCompileTo(''); -    expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar'); - -    expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') -      .withInput({ -        foo: ['bar', 'baz'], -      }) -      .toCompileTo('barbaz'); -  }); - -  it('pass number literals', () => { -    expectTemplate('{{12}}').toCompileTo(''); -    expectTemplate('{{12}}').withInput({ '12': 'bar' }).toCompileTo('bar'); -    expectTemplate('{{12.34}}').toCompileTo(''); -    expectTemplate('{{12.34}}').withInput({ '12.34': 'bar' }).toCompileTo('bar'); -    expectTemplate('{{12.34 1}}') -      .withInput({ -        '12.34'(arg: any) { -          return 'bar' + arg; -        }, -      }) -      .toCompileTo('bar1'); -  }); - -  it('pass boolean literals', () => { -    expectTemplate('{{true}}').toCompileTo(''); -    expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo(''); -    expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo'); -  }); - -  it('should handle literals in subexpression', () => { -    expectTemplate('{{foo (false)}}') -      .withInput({ -        false() { -          return 'bar'; -        }, -      }) -      .withHelper('foo', function (arg) { -        return arg; -      }) -      .toCompileTo('bar'); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.blocks.test.ts b/dev/lib/handlebars/src/spec/index.blocks.test.ts deleted file mode 100644 index 2d9a8707..00000000 --- a/dev/lib/handlebars/src/spec/index.blocks.test.ts +++ /dev/null @@ -1,366 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars, { type HelperOptions } from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('blocks', () => { -  it('array', () => { -    const string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; - -    expectTemplate(string) -      .withInput({ -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -        world: 'world', -      }) -      .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - -    expectTemplate(string) -      .withInput({ -        goodbyes: [], -        world: 'world', -      }) -      .toCompileTo('cruel world!'); -  }); - -  it('array without data', () => { -    expectTemplate('{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}') -      .withInput({ -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -        world: 'world', -      }) -      .toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'); -  }); - -  it('array with @index', () => { -    expectTemplate('{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!') -      .withInput({ -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -        world: 'world', -      }) -      .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); -  }); - -  it('empty block', () => { -    const string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; - -    expectTemplate(string) -      .withInput({ -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -        world: 'world', -      }) -      .toCompileTo('cruel world!'); - -    expectTemplate(string) -      .withInput({ -        goodbyes: [], -        world: 'world', -      }) -      .toCompileTo('cruel world!'); -  }); - -  it('block with complex lookup', () => { -    expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}') -      .withInput({ -        name: 'Alan', -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -      }) -      .toCompileTo('goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '); -  }); - -  it('multiple blocks with complex lookup', () => { -    expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}') -      .withInput({ -        name: 'Alan', -        goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -      }) -      .toCompileTo('AlanAlanAlanAlanAlanAlan'); -  }); - -  it('block with complex lookup using nested context', () => { -    expectTemplate('{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}').toThrow(Error); -  }); - -  it('block with deep nested complex lookup', () => { -    expectTemplate( -      '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}' -    ) -      .withInput({ -        omg: 'OMG!', -        outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }], -      }) -      .toCompileTo('Goodbye cruel sad OMG!'); -  }); - -  it('works with cached blocks', () => { -    expectTemplate('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}') -      .withCompileOptions({ data: false }) -      .withInput({ -        person: [ -          { first: 'Alan', last: 'Johnson' }, -          { first: 'Alan', last: 'Johnson' }, -        ], -      }) -      .toCompileTo('Alan JohnsonAlan Johnson'); -  }); - -  describe('inverted sections', () => { -    it('inverted sections with unset value', () => { -      expectTemplate( -        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' -      ).toCompileTo('Right On!'); -    }); - -    it('inverted section with false value', () => { -      expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') -        .withInput({ goodbyes: false }) -        .toCompileTo('Right On!'); -    }); - -    it('inverted section with empty set', () => { -      expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') -        .withInput({ goodbyes: [] }) -        .toCompileTo('Right On!'); -    }); - -    it('block inverted sections', () => { -      expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people'); -    }); - -    it('chained inverted sections', () => { -      expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people'); - -      expectTemplate( -        '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}' -      ) -        .withInput({ none: 'No people' }) -        .toCompileTo('No people'); - -      expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people'); -    }); - -    it('chained inverted sections with mismatch', () => { -      expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}').toThrow(Error); -    }); - -    it('block inverted sections with empty arrays', () => { -      expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') -        .withInput({ -          none: 'No people', -          people: [], -        }) -        .toCompileTo('No people'); -    }); -  }); - -  describe('standalone sections', () => { -    it('block standalone else sections', () => { -      expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people\n'); - -      expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people\n'); - -      expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people\n'); -    }); - -    it('block standalone chained else sections', () => { -      expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people\n'); - -      expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n') -        .withInput({ none: 'No people' }) -        .toCompileTo('No people\n'); -    }); - -    it('should handle nesting', () => { -      expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') -        .withInput({ -          data: [1, 3, 5], -        }) -        .toCompileTo('1\n3\n5\nOK.'); -    }); -  }); - -  describe('decorators', () => { -    it('should apply mustache decorators', () => { -      expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') -        .withHelper('helper', function (options: HelperOptions) { -          return (options.fn as any).run; -        }) -        .withDecorator('decorator', function (fn) { -          (fn as any).run = 'success'; -          return fn; -        }) -        .toCompileTo('success'); -    }); - -    it('should apply allow undefined return', () => { -      expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') -        .withHelper('helper', function (options: HelperOptions) { -          return options.fn() + (options.fn as any).run; -        }) -        .withDecorator('decorator', function (fn) { -          (fn as any).run = 'cess'; -        }) -        .toCompileTo('success'); -    }); - -    it('should apply block decorators', () => { -      expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}') -        .withHelper('helper', function (options: HelperOptions) { -          return (options.fn as any).run; -        }) -        .withDecorator('decorator', function (fn, props, container, options) { -          (fn as any).run = options.fn(); -          return fn; -        }) -        .toCompileTo('success'); -    }); - -    it('should support nested decorators', () => { -      expectTemplate( -        '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' -      ) -        .withHelper('helper', function (options: HelperOptions) { -          return (options.fn as any).run; -        }) -        .withDecorators({ -          decorator(fn, props, container, options) { -            (fn as any).run = options.fn.nested + options.fn(); -            return fn; -          }, -          nested(fn, props, container, options) { -            props.nested = options.fn(); -          }, -        }) -        .toCompileTo('success'); -    }); - -    it('should apply multiple decorators', () => { -      expectTemplate( -        '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' -      ) -        .withHelper('helper', function (options: HelperOptions) { -          return (options.fn as any).run; -        }) -        .withDecorator('decorator', function (fn, props, container, options) { -          (fn as any).run = ((fn as any).run || '') + options.fn(); -          return fn; -        }) -        .toCompileTo('success'); -    }); - -    it('should access parent variables', () => { -      expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') -        .withHelper('helper', function (options: HelperOptions) { -          return (options.fn as any).run; -        }) -        .withDecorator('decorator', function (fn, props, container, options) { -          (fn as any).run = options.args; -          return fn; -        }) -        .withInput({ foo: 'success' }) -        .toCompileTo('success'); -    }); - -    it('should work with root program', () => { -      let run; -      expectTemplate('{{*decorator "success"}}') -        .withDecorator('decorator', function (fn, props, container, options) { -          expect(options.args[0]).toEqual('success'); -          run = true; -          return fn; -        }) -        .withInput({ foo: 'success' }) -        .toCompileTo(''); -      expect(run).toEqual(true); -    }); - -    it('should fail when accessing variables from root', () => { -      let run; -      expectTemplate('{{*decorator foo}}') -        .withDecorator('decorator', function (fn, props, container, options) { -          expect(options.args[0]).toBeUndefined(); -          run = true; -          return fn; -        }) -        .withInput({ foo: 'fail' }) -        .toCompileTo(''); -      expect(run).toEqual(true); -    }); - -    describe('registration', () => { -      beforeEach(() => { -        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 = {}; - -        kbnHandlebarsEnv!.registerDecorator('foo', function () { -          return 'fail'; -        }); - -        expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); -        kbnHandlebarsEnv!.unregisterDecorator('foo'); -        expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); -      }); - -      it('allows multiple globals', () => { -        // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. -        kbnHandlebarsEnv!.decorators = {}; - -        // @ts-expect-error: Expected 2 arguments, but got 1. -        kbnHandlebarsEnv!.registerDecorator({ -          foo() {}, -          bar() {}, -        }); - -        expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); -        expect(!!kbnHandlebarsEnv!.decorators.bar).toEqual(true); -        kbnHandlebarsEnv!.unregisterDecorator('foo'); -        kbnHandlebarsEnv!.unregisterDecorator('bar'); -        expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); -        expect(kbnHandlebarsEnv!.decorators.bar).toBeUndefined(); -      }); - -      it('fails with multiple and args', () => { -        expect(() => { -          kbnHandlebarsEnv!.registerDecorator( -            // @ts-expect-error: Argument of type '{ world(): string; testHelper(): string; }' is not assignable to parameter of type 'string'. -            { -              world() { -                return 'world!'; -              }, -              testHelper() { -                return 'found it!'; -              }, -            }, -            {} -          ); -        }).toThrow('Arg not supported with multiple decorators'); -      }); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.builtins.test.ts b/dev/lib/handlebars/src/spec/index.builtins.test.ts deleted file mode 100644 index c47ec29f..00000000 --- a/dev/lib/handlebars/src/spec/index.builtins.test.ts +++ /dev/null @@ -1,676 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -/* eslint-disable max-classes-per-file */ - -import Handlebars from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('builtin helpers', () => { -  describe('#if', () => { -    it('if', () => { -      const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; - -      expectTemplate(string) -        .withInput({ -          goodbye: true, -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye: 'dummy', -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye: false, -          world: 'world', -        }) -        .toCompileTo('cruel world!'); - -      expectTemplate(string).withInput({ world: 'world' }).toCompileTo('cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye: ['foo'], -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye: [], -          world: 'world', -        }) -        .toCompileTo('cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye: 0, -          world: 'world', -        }) -        .toCompileTo('cruel world!'); - -      expectTemplate('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!') -        .withInput({ -          goodbye: 0, -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); -    }); - -    it('if with function argument', () => { -      const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; - -      expectTemplate(string) -        .withInput({ -          goodbye() { -            return true; -          }, -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye() { -            return this.world; -          }, -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye() { -            return false; -          }, -          world: 'world', -        }) -        .toCompileTo('cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbye() { -            return this.foo; -          }, -          world: 'world', -        }) -        .toCompileTo('cruel world!'); -    }); - -    it('should not change the depth list', () => { -      expectTemplate('{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}') -        .withInput({ -          foo: { goodbye: true }, -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel world!'); -    }); -  }); - -  describe('#with', () => { -    it('with', () => { -      expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') -        .withInput({ -          person: { -            first: 'Alan', -            last: 'Johnson', -          }, -        }) -        .toCompileTo('Alan Johnson'); -    }); - -    it('with with function argument', () => { -      expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') -        .withInput({ -          person() { -            return { -              first: 'Alan', -              last: 'Johnson', -            }; -          }, -        }) -        .toCompileTo('Alan Johnson'); -    }); - -    it('with with else', () => { -      expectTemplate( -        '{{#with person}}Person is present{{else}}Person is not present{{/with}}' -      ).toCompileTo('Person is not present'); -    }); - -    it('with provides block parameter', () => { -      expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') -        .withInput({ -          person: { -            first: 'Alan', -            last: 'Johnson', -          }, -        }) -        .toCompileTo('Alan Johnson'); -    }); - -    it('works when data is disabled', () => { -      expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') -        .withInput({ person: { first: 'Alan', last: 'Johnson' } }) -        .withCompileOptions({ data: false }) -        .toCompileTo('Alan Johnson'); -    }); -  }); - -  describe('#each', () => { -    it('each', () => { -      const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - -      expectTemplate(string) -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbyes: [], -          world: 'world', -        }) -        .toCompileTo('cruel world!'); -    }); - -    it('each without data', () => { -      expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .withRuntimeOptions({ data: false }) -        .withCompileOptions({ data: false }) -        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - -      expectTemplate('{{#each .}}{{.}}{{/each}}') -        .withInput({ goodbyes: 'cruel', world: 'world' }) -        .withRuntimeOptions({ data: false }) -        .withCompileOptions({ data: false }) -        .toCompileTo('cruelworld'); -    }); - -    it('each without context', () => { -      expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') -        .withInput(undefined) -        .toCompileTo('cruel !'); -    }); - -    it('each with an object and @key', () => { -      const string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; - -      function Clazz(this: any) { -        this['<b>#1</b>'] = { text: 'goodbye' }; -        this[2] = { text: 'GOODBYE' }; -      } -      Clazz.prototype.foo = 'fail'; -      const hash = { goodbyes: new (Clazz as any)(), world: 'world' }; - -      // Object property iteration order is undefined according to ECMA spec, -      // so we need to check both possible orders -      // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop -      try { -        expectTemplate(string) -          .withInput(hash) -          .toCompileTo('<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'); -      } catch (e) { -        expectTemplate(string) -          .withInput(hash) -          .toCompileTo('2. GOODBYE! <b>#1</b>. goodbye! cruel world!'); -      } - -      expectTemplate(string) -        .withInput({ -          goodbyes: {}, -          world: 'world', -        }) -        .toCompileTo('cruel world!'); -    }); - -    it('each with @index', () => { -      expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); -    }); - -    it('each with nested @index', () => { -      expectTemplate( -        '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!' -      ) -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo( -          '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!' -        ); -    }); - -    it('each with block params', () => { -      expectTemplate( -        '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!' -      ) -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }], -          world: 'world', -        }) -        .toCompileTo('0. goodbye!  0 0 0 1 After 0 1. Goodbye!  1 0 1 1 After 1 cruel world!'); -    }); - -    // TODO: This test has been added to the `4.x` branch of the handlebars.js repo along with a code-fix, -    // but a new version of the handlebars package containing this fix has not yet been published to npm. -    // -    // Before enabling this code, a new version of handlebars needs to be released and the corresponding -    // updates needs to be applied to this implementation. -    // -    // See: https://github.com/handlebars-lang/handlebars.js/commit/30dbf0478109ded8f12bb29832135d480c17e367 -    it.skip('each with block params and strict compilation', () => { -      expectTemplate('{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}') -        .withCompileOptions({ strict: true }) -        .withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] }) -        .toCompileTo('0. goodbye!1. Goodbye!'); -    }); - -    it('each object with @index', () => { -      expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: { -            a: { text: 'goodbye' }, -            b: { text: 'Goodbye' }, -            c: { text: 'GOODBYE' }, -          }, -          world: 'world', -        }) -        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); -    }); - -    it('each with @first', () => { -      expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('goodbye! cruel world!'); -    }); - -    it('each with nested @first', () => { -      expectTemplate( -        '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' -      ) -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'); -    }); - -    it('each object with @first', () => { -      expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, -          world: 'world', -        }) -        .toCompileTo('goodbye! cruel world!'); -    }); - -    it('each with @last', () => { -      expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('GOODBYE! cruel world!'); -    }); - -    it('each object with @last', () => { -      expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, -          world: 'world', -        }) -        .toCompileTo('Goodbye! cruel world!'); -    }); - -    it('each with nested @last', () => { -      expectTemplate( -        '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' -      ) -        .withInput({ -          goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -          world: 'world', -        }) -        .toCompileTo('(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'); -    }); - -    it('each with function argument', () => { -      const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - -      expectTemplate(string) -        .withInput({ -          goodbyes() { -            return [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]; -          }, -          world: 'world', -        }) -        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbyes: [], -          world: 'world', -        }) -        .toCompileTo('cruel world!'); -    }); - -    it('each object when last key is an empty string', () => { -      expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -        .withInput({ -          goodbyes: { -            a: { text: 'goodbye' }, -            b: { text: 'Goodbye' }, -            '': { text: 'GOODBYE' }, -          }, -          world: 'world', -        }) -        .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); -    }); - -    it('data passed to helpers', () => { -      expectTemplate('{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}') -        .withInput({ letters: ['a', 'b', 'c'] }) -        .withHelper('detectDataInsideEach', function (options) { -          return options.data && options.data.exclaim; -        }) -        .withRuntimeOptions({ -          data: { -            exclaim: '!', -          }, -        }) -        .toCompileTo('a!b!c!'); -    }); - -    it('each on implicit context', () => { -      expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(Handlebars.Exception); -    }); - -    it('each on iterable', () => { -      class Iterator { -        private arr: any[]; -        private index: number = 0; - -        constructor(arr: any[]) { -          this.arr = arr; -        } - -        next() { -          const value = this.arr[this.index]; -          const done = this.index === this.arr.length; -          if (!done) { -            this.index++; -          } -          return { value, done }; -        } -      } - -      class Iterable { -        private arr: any[]; - -        constructor(arr: any[]) { -          this.arr = arr; -        } - -        [Symbol.iterator]() { -          return new Iterator(this.arr); -        } -      } - -      const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - -      expectTemplate(string) -        .withInput({ -          goodbyes: new Iterable([{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]), -          world: 'world', -        }) -        .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - -      expectTemplate(string) -        .withInput({ -          goodbyes: new Iterable([]), -          world: 'world', -        }) -        .toCompileTo('cruel world!'); -    }); -  }); - -  describe('#log', function () { -    /* eslint-disable no-console */ -    let $log: typeof console.log; -    let $info: typeof console.info; -    let $error: typeof console.error; - -    beforeEach(function () { -      $log = console.log; -      $info = console.info; -      $error = console.error; - -      global.kbnHandlebarsEnv = Handlebars.create(); -    }); - -    afterEach(function () { -      console.log = $log; -      console.info = $info; -      console.error = $error; - -      global.kbnHandlebarsEnv = null; -    }); - -    it('should call logger at default level', function () { -      let levelArg; -      let logArg; -      kbnHandlebarsEnv!.log = function (level, arg) { -        levelArg = level; -        logArg = arg; -      }; - -      expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(1).toEqual(levelArg); -      expect('whee').toEqual(logArg); -    }); - -    it('should call logger at data level', function () { -      let levelArg; -      let logArg; -      kbnHandlebarsEnv!.log = function (level, arg) { -        levelArg = level; -        logArg = arg; -      }; - -      expectTemplate('{{log blah}}') -        .withInput({ blah: 'whee' }) -        .withRuntimeOptions({ data: { level: '03' } }) -        .withCompileOptions({ data: true }) -        .toCompileTo(''); -      expect('03').toEqual(levelArg); -      expect('whee').toEqual(logArg); -    }); - -    it('should output to info', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.info = function (info) { -        expect('whee').toEqual(info); -        calls++; -        if (calls === callsExpected) { -          console.info = $info; -          console.log = $log; -        } -      }; -      console.log = function (log) { -        expect('whee').toEqual(log); -        calls++; -        if (calls === callsExpected) { -          console.info = $info; -          console.log = $log; -        } -      }; - -      expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should log at data level', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.error = function (log) { -        expect('whee').toEqual(log); -        calls++; -        if (calls === callsExpected) console.error = $error; -      }; - -      expectTemplate('{{log blah}}') -        .withInput({ blah: 'whee' }) -        .withRuntimeOptions({ data: { level: '03' } }) -        .withCompileOptions({ data: true }) -        .toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should handle missing logger', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      // @ts-expect-error -      console.error = undefined; -      console.log = function (log) { -        expect('whee').toEqual(log); -        calls++; -        if (calls === callsExpected) console.log = $log; -      }; - -      expectTemplate('{{log blah}}') -        .withInput({ blah: 'whee' }) -        .withRuntimeOptions({ data: { level: '03' } }) -        .withCompileOptions({ data: true }) -        .toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should handle string log levels', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.error = function (log) { -        expect('whee').toEqual(log); -        calls++; -      }; - -      expectTemplate('{{log blah}}') -        .withInput({ blah: 'whee' }) -        .withRuntimeOptions({ data: { level: 'error' } }) -        .withCompileOptions({ data: true }) -        .toCompileTo(''); -      expect(calls).toEqual(callsExpected); - -      calls = 0; - -      expectTemplate('{{log blah}}') -        .withInput({ blah: 'whee' }) -        .withRuntimeOptions({ data: { level: 'ERROR' } }) -        .withCompileOptions({ data: true }) -        .toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should handle hash log levels [1]', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.error = function (log) { -        expect('whee').toEqual(log); -        calls++; -      }; - -      expectTemplate('{{log blah level="error"}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should handle hash log levels [2]', function () { -      let called = false; - -      console.info = -        console.log = -        console.error = -        console.debug = -          function () { -            called = true; -            console.info = console.log = console.error = console.debug = $log; -          }; - -      expectTemplate('{{log blah level="debug"}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(false).toEqual(called); -    }); - -    it('should pass multiple log arguments', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.info = console.log = function (log1, log2, log3) { -        expect('whee').toEqual(log1); -        expect('foo').toEqual(log2); -        expect(1).toEqual(log3); -        calls++; -        if (calls === callsExpected) console.log = $log; -      }; - -      expectTemplate('{{log blah "foo" 1}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); - -    it('should pass zero log arguments', function () { -      let calls = 0; -      const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - -      console.info = console.log = function () { -        expect(arguments.length).toEqual(0); -        calls++; -        if (calls === callsExpected) console.log = $log; -      }; - -      expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo(''); -      expect(calls).toEqual(callsExpected); -    }); -    /* eslint-enable no-console */ -  }); - -  describe('#lookup', () => { -    it('should lookup arbitrary content', () => { -      expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}') -        .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) -        .toCompileTo('foobar'); -    }); - -    it('should not fail on undefined value', () => { -      expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}') -        .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) -        .toCompileTo(''); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.compiler.test.ts b/dev/lib/handlebars/src/spec/index.compiler.test.ts deleted file mode 100644 index ef5c55f2..00000000 --- a/dev/lib/handlebars/src/spec/index.compiler.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from '../..'; -import { forEachCompileFunctionName } from '../__jest__/test_bench'; - -describe('compiler', () => { -  forEachCompileFunctionName((compileName) => { -    const compile = Handlebars[compileName].bind(Handlebars); - -    describe(`#${compileName}`, () => { -      it('should fail with invalid input', () => { -        expect(function () { -          compile(null); -        }).toThrow( -          `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed null` -        ); - -        expect(function () { -          compile({}); -        }).toThrow( -          `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed [object Object]` -        ); -      }); - -      it('should include the location in the error (row and column)', () => { -        try { -          compile(' \n  {{#if}}\n{{/def}}')(); -          expect(true).toEqual(false); -        } catch (err) { -          expect(err.message).toEqual("if doesn't match def - 2:5"); -          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) -            // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. -            expect(err.column).toEqual(5); -          } -          expect(err.lineNumber).toEqual(2); -        } -      }); - -      it('should include the location as enumerable property', () => { -        try { -          compile(' \n  {{#if}}\n{{/def}}')(); -          expect(true).toEqual(false); -        } catch (err) { -          expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true); -        } -      }); - -      it('can utilize AST instance', () => { -        expect( -          compile({ -            type: 'Program', -            body: [{ type: 'ContentStatement', value: 'Hello' }], -          })() -        ).toEqual('Hello'); -      }); - -      it('can pass through an empty string', () => { -        expect(compile('')()).toEqual(''); -      }); - -      it('should not modify the options.data property(GH-1327)', () => { -        // 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) -        ); -      }); - -      it('should not modify the options.knownHelpers property(GH-1327)', () => { -        const options = { knownHelpers: {} }; -        compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); -        expect(JSON.stringify(options, null, 2)).toEqual( -          JSON.stringify({ knownHelpers: {} }, null, 2) -        ); -      }); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.data.test.ts b/dev/lib/handlebars/src/spec/index.data.test.ts deleted file mode 100644 index 94d3b51c..00000000 --- a/dev/lib/handlebars/src/spec/index.data.test.ts +++ /dev/null @@ -1,269 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars, { type HelperOptions } from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('data', () => { -  it('passing in data to a compiled function that expects data - works with helpers', () => { -    expectTemplate('{{hello}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (this: any, options) { -        return options.data.adjective + ' ' + this.noun; -      }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .withInput({ noun: 'cat' }) -      .toCompileTo('happy cat'); -  }); - -  it('data can be looked up via @foo', () => { -    expectTemplate('{{@hello}}') -      .withRuntimeOptions({ data: { hello: 'hello' } }) -      .toCompileTo('hello'); -  }); - -  it('deep @foo triggers automatic top-level data', () => { -    global.kbnHandlebarsEnv = Handlebars.create(); -    const helpers = Handlebars.createFrame(kbnHandlebarsEnv!.helpers); - -    helpers.let = function (options: HelperOptions) { -      const frame = Handlebars.createFrame(options.data); - -      for (const prop in options.hash) { -        if (prop in options.hash) { -          frame[prop] = options.hash[prop]; -        } -      } -      return options.fn(this, { data: frame }); -    }; - -    expectTemplate( -      '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}' -    ) -      .withInput({ foo: true }) -      .withHelpers(helpers) -      .toCompileTo('Hello world'); - -    global.kbnHandlebarsEnv = null; -  }); - -  it('parameter data can be looked up via @foo', () => { -    expectTemplate('{{hello @world}}') -      .withRuntimeOptions({ data: { world: 'world' } }) -      .withHelper('hello', function (noun) { -        return 'Hello ' + noun; -      }) -      .toCompileTo('Hello world'); -  }); - -  it('hash values can be looked up via @foo', () => { -    expectTemplate('{{hello noun=@world}}') -      .withRuntimeOptions({ data: { world: 'world' } }) -      .withHelper('hello', function (options) { -        return 'Hello ' + options.hash.noun; -      }) -      .toCompileTo('Hello world'); -  }); - -  it('nested parameter data can be looked up via @foo.bar', () => { -    expectTemplate('{{hello @world.bar}}') -      .withRuntimeOptions({ data: { world: { bar: 'world' } } }) -      .withHelper('hello', function (noun) { -        return 'Hello ' + noun; -      }) -      .toCompileTo('Hello world'); -  }); - -  it('nested parameter data does not fail with @world.bar', () => { -    expectTemplate('{{hello @world.bar}}') -      .withRuntimeOptions({ data: { foo: { bar: 'world' } } }) -      .withHelper('hello', function (noun) { -        return 'Hello ' + noun; -      }) -      .toCompileTo('Hello undefined'); -  }); - -  it('parameter data throws when using complex scope references', () => { -    expectTemplate('{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}').toThrow(Error); -  }); - -  it('data can be functions', () => { -    expectTemplate('{{@hello}}') -      .withRuntimeOptions({ -        data: { -          hello() { -            return 'hello'; -          }, -        }, -      }) -      .toCompileTo('hello'); -  }); - -  it('data can be functions with params', () => { -    expectTemplate('{{@hello "hello"}}') -      .withRuntimeOptions({ -        data: { -          hello(arg: any) { -            return arg; -          }, -        }, -      }) -      .toCompileTo('hello'); -  }); - -  it('data is inherited downstream', () => { -    expectTemplate( -      '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}' -    ) -      .withInput({ bar: { baz: 'hello world' } }) -      .withCompileOptions({ data: true }) -      .withHelper('let', function (this: any, options) { -        const frame = Handlebars.createFrame(options.data); -        for (const prop in options.hash) { -          if (prop in options.hash) { -            frame[prop] = options.hash[prop]; -          } -        } -        return options.fn(this, { data: frame }); -      }) -      .withRuntimeOptions({ data: {} }) -      .toCompileTo('2hello world1'); -  }); - -  it('passing in data to a compiled function that expects data - works with helpers in partials', () => { -    expectTemplate('{{>myPartial}}') -      .withCompileOptions({ data: true }) -      .withPartial('myPartial', '{{hello}}') -      .withHelper('hello', function (this: any, options: HelperOptions) { -        return options.data.adjective + ' ' + this.noun; -      }) -      .withInput({ noun: 'cat' }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('happy cat'); -  }); - -  it('passing in data to a compiled function that expects data - works with helpers and parameters', () => { -    expectTemplate('{{hello world}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (this: any, noun, options) { -        return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : ''); -      }) -      .withInput({ exclaim: true, world: 'world' }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('happy world!'); -  }); - -  it('passing in data to a compiled function that expects data - works with block helpers', () => { -    expectTemplate('{{#hello}}{{world}}{{/hello}}') -      .withCompileOptions({ -        data: true, -      }) -      .withHelper('hello', function (this: any, options) { -        return options.fn(this); -      }) -      .withHelper('world', function (this: any, options) { -        return options.data.adjective + ' world' + (this.exclaim ? '!' : ''); -      }) -      .withInput({ exclaim: true }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('happy world!'); -  }); - -  it('passing in data to a compiled function that expects data - works with block helpers that use ..', () => { -    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (options) { -        return options.fn({ exclaim: '?' }); -      }) -      .withHelper('world', function (this: any, thing, options) { -        return options.data.adjective + ' ' + thing + (this.exclaim || ''); -      }) -      .withInput({ exclaim: true, zomg: 'world' }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('happy world?'); -  }); - -  it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', () => { -    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (options) { -        return options.data.accessData + ' ' + options.fn({ exclaim: '?' }); -      }) -      .withHelper('world', function (this: any, thing, options) { -        return options.data.adjective + ' ' + thing + (this.exclaim || ''); -      }) -      .withInput({ exclaim: true, zomg: 'world' }) -      .withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } }) -      .toCompileTo('#win happy world?'); -  }); - -  it('you can override inherited data when invoking a helper', () => { -    expectTemplate('{{#hello}}{{world zomg}}{{/hello}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (options) { -        return options.fn({ exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } }); -      }) -      .withHelper('world', function (this: any, thing, options) { -        return options.data.adjective + ' ' + thing + (this.exclaim || ''); -      }) -      .withInput({ exclaim: true, zomg: 'planet' }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('sad world?'); -  }); - -  it('you can override inherited data when invoking a helper with depth', () => { -    expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') -      .withCompileOptions({ data: true }) -      .withHelper('hello', function (options) { -        return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } }); -      }) -      .withHelper('world', function (this: any, thing, options) { -        return options.data.adjective + ' ' + thing + (this.exclaim || ''); -      }) -      .withInput({ exclaim: true, zomg: 'world' }) -      .withRuntimeOptions({ data: { adjective: 'happy' } }) -      .toCompileTo('sad world?'); -  }); - -  describe('@root', () => { -    it('the root context can be looked up via @root', () => { -      expectTemplate('{{@root.foo}}') -        .withInput({ foo: 'hello' }) -        .withRuntimeOptions({ data: {} }) -        .toCompileTo('hello'); - -      expectTemplate('{{@root.foo}}').withInput({ foo: 'hello' }).toCompileTo('hello'); -    }); - -    it('passed root values take priority', () => { -      expectTemplate('{{@root.foo}}') -        .withInput({ foo: 'should not be used' }) -        .withRuntimeOptions({ data: { root: { foo: 'hello' } } }) -        .toCompileTo('hello'); -    }); -  }); - -  describe('nesting', () => { -    it('the root context can be looked up via @root', () => { -      expectTemplate( -        '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' -      ) -        .withInput({ foo: 'hello' }) -        .withHelper('helper', function (this: any, options) { -          const frame = Handlebars.createFrame(options.data); -          frame.depth = options.data.depth + 1; -          return options.fn(this, { data: frame }); -        }) -        .withRuntimeOptions({ -          data: { -            depth: 0, -          }, -        }) -        .toCompileTo('2 1 0'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.helpers.test.ts b/dev/lib/handlebars/src/spec/index.helpers.test.ts deleted file mode 100644 index 4cfa39bb..00000000 --- a/dev/lib/handlebars/src/spec/index.helpers.test.ts +++ /dev/null @@ -1,958 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars, { type HelperOptions } from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -beforeEach(() => { -  global.kbnHandlebarsEnv = Handlebars.create(); -}); - -afterEach(() => { -  global.kbnHandlebarsEnv = null; -}); - -describe('helpers', () => { -  it('helper with complex lookup$', () => { -    expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}') -      .withInput({ -        prefix: '/root', -        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -      }) -      .withHelper('link', function (this: any, prefix) { -        return '<a href="' + prefix + '/' + this.url + '">' + this.text + '</a>'; -      }) -      .toCompileTo('<a href="/root/goodbye">Goodbye</a>'); -  }); - -  it('helper for raw block gets raw content', () => { -    expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}') -      .withInput({ test: 'hello' }) -      .withHelper('raw', function (options: HelperOptions) { -        return options.fn(); -      }) -      .toCompileTo(' {{test}} '); -  }); - -  it('helper for raw block gets parameters', () => { -    expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}') -      .withInput({ test: 'hello' }) -      .withHelper('raw', function (a, b, c, options: HelperOptions) { -        const ret = options.fn() + a + b + c; -        return ret; -      }) -      .toCompileTo(' {{test}} 123'); -  }); - -  describe('raw block parsing (with identity helper-function)', () => { -    function runWithIdentityHelper(template: string, expected: string) { -      expectTemplate(template) -        .withHelper('identity', function (options: HelperOptions) { -          return options.fn(); -        }) -        .toCompileTo(expected); -    } - -    it('helper for nested raw block gets raw content', () => { -      runWithIdentityHelper( -        '{{{{identity}}}} {{{{b}}}} {{{{/b}}}} {{{{/identity}}}}', -        ' {{{{b}}}} {{{{/b}}}} ' -      ); -    }); - -    it('helper for nested raw block works with empty content', () => { -      runWithIdentityHelper('{{{{identity}}}}{{{{/identity}}}}', ''); -    }); - -    it.skip('helper for nested raw block works if nested raw blocks are broken', () => { -      // This test was introduced in 4.4.4, but it was not the actual problem that lead to the patch release -      // The test is deactivated, because in 3.x this template cases an exception and it also does not work in 4.4.3 -      // If anyone can make this template work without breaking everything else, then go for it, -      // but for now, this is just a known bug, that will be documented. -      runWithIdentityHelper( -        '{{{{identity}}}} {{{{a}}}} {{{{ {{{{/ }}}} }}}} {{{{/identity}}}}', -        ' {{{{a}}}} {{{{ {{{{/ }}}} }}}} ' -      ); -    }); - -    it('helper for nested raw block closes after first matching close', () => { -      runWithIdentityHelper( -        '{{{{identity}}}}abc{{{{/identity}}}} {{{{identity}}}}abc{{{{/identity}}}}', -        'abc abc' -      ); -    }); - -    it('helper for nested raw block throw exception when with missing closing braces', () => { -      const string = '{{{{a}}}} {{{{/a'; -      expectTemplate(string).toThrow(); -    }); -  }); - -  it('helper block with identical context', () => { -    expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}') -      .withInput({ name: 'Alan' }) -      .withHelper('goodbyes', function (this: any, options: HelperOptions) { -        let out = ''; -        const byes = ['Goodbye', 'goodbye', 'GOODBYE']; -        for (let i = 0, j = byes.length; i < j; i++) { -          out += byes[i] + ' ' + options.fn(this) + '! '; -        } -        return out; -      }) -      .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); -  }); - -  it('helper block with complex lookup expression', () => { -    expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}') -      .withInput({ name: 'Alan' }) -      .withHelper('goodbyes', function (options: HelperOptions) { -        let out = ''; -        const byes = ['Goodbye', 'goodbye', 'GOODBYE']; -        for (let i = 0, j = byes.length; i < j; i++) { -          out += byes[i] + ' ' + options.fn({}) + '! '; -        } -        return out; -      }) -      .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); -  }); - -  it('helper with complex lookup and nested template', () => { -    expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') -      .withInput({ -        prefix: '/root', -        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -      }) -      .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>'); -  }); - -  it('helper with complex lookup and nested template in VM+Compiler', () => { -    expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') -      .withInput({ -        prefix: '/root', -        goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -      }) -      .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>'); -  }); - -  it('helper returning undefined value', () => { -    expectTemplate(' {{nothere}}') -      .withHelpers({ -        nothere() {}, -      }) -      .toCompileTo(' '); - -    expectTemplate(' {{#nothere}}{{/nothere}}') -      .withHelpers({ -        nothere() {}, -      }) -      .toCompileTo(' '); -  }); - -  it('block helper', () => { -    expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!') -      .withInput({ world: 'world' }) -      .withHelper('goodbyes', function (options: HelperOptions) { -        return options.fn({ text: 'GOODBYE' }); -      }) -      .toCompileTo('GOODBYE! cruel world!'); -  }); - -  it('block helper staying in the same context', () => { -    expectTemplate('{{#form}}<p>{{name}}</p>{{/form}}') -      .withInput({ name: 'Yehuda' }) -      .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: HelperOptions) { -      return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>'; -    } - -    expectTemplate('<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>') -      .withInput({ -        people: [ -          { name: 'Alan', id: 1 }, -          { name: 'Yehuda', id: 2 }, -        ], -      }) -      .withHelper('link', link) -      .toCompileTo( -        '<ul><li><a href="/people/1">Alan</a></li><li><a href="/people/2">Yehuda</a></li></ul>' -      ); -  }); - -  it('block helper for undefined value', () => { -    expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo(''); -  }); - -  it('block helper passing a new context', () => { -    expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{/form}}') -      .withInput({ yehuda: { name: 'Yehuda' } }) -      .withHelper('form', function (context, options: HelperOptions) { -        return '<form>' + options.fn(context) + '</form>'; -      }) -      .toCompileTo('<form><p>Yehuda</p></form>'); -  }); - -  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: HelperOptions) { -        return '<form>' + options.fn(context) + '</form>'; -      }) -      .toCompileTo('<form><p>Harold</p></form>'); -  }); - -  it('nested block helpers', () => { -    expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}') -      .withInput({ -        yehuda: { name: 'Yehuda' }, -      }) -      .withHelper('link', function (this: any, options: HelperOptions) { -        return '<a href="' + this.name + '">' + options.fn(this) + '</a>'; -      }) -      .withHelper('form', function (context, options: HelperOptions) { -        return '<form>' + options.fn(context) + '</form>'; -      }) -      .toCompileTo('<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>'); -  }); - -  it('block helper inverted sections', () => { -    const string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"; -    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++) { -          out += '<li>'; -          out += options.fn(context[i]); -          out += '</li>'; -        } -        out += '</ul>'; -        return out; -      } else { -        return '<p>' + options.inverse(this) + '</p>'; -      } -    } - -    // the meaning here may be kind of hard to catch, but list.not is always called, -    // so we should see the output of both -    expectTemplate(string) -      .withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] }) -      .withHelpers({ list }) -      .toCompileTo('<ul><li>Alan</li><li>Yehuda</li></ul>'); - -    expectTemplate(string) -      .withInput({ people: [] }) -      .withHelpers({ list }) -      .toCompileTo("<p><em>Nobody's here</em></p>"); - -    expectTemplate('{{#list people}}Hello{{^}}{{message}}{{/list}}') -      .withInput({ -        people: [], -        message: "Nobody's here", -      }) -      .withHelpers({ list }) -      .toCompileTo('<p>Nobody's here</p>'); -  }); - -  it('pathed lambas with parameters', () => { -    const hash = { -      helper: () => 'winning', -    }; -    // @ts-expect-error -    hash.hash = hash; - -    const helpers = { -      './helper': () => 'fail', -    }; - -    expectTemplate('{{./helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); -    expectTemplate('{{hash/helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); -  }); - -  describe('helpers hash', () => { -    it('providing a helpers hash', () => { -      expectTemplate('Goodbye {{cruel}} {{world}}!') -        .withInput({ cruel: 'cruel' }) -        .withHelpers({ -          world() { -            return 'world'; -          }, -        }) -        .toCompileTo('Goodbye cruel world!'); - -      expectTemplate('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!') -        .withInput({ iter: [{ cruel: 'cruel' }] }) -        .withHelpers({ -          world() { -            return 'world'; -          }, -        }) -        .toCompileTo('Goodbye cruel world!'); -    }); - -    it('in cases of conflict, helpers win', () => { -      expectTemplate('{{{lookup}}}') -        .withInput({ lookup: 'Explicit' }) -        .withHelpers({ -          lookup() { -            return 'helpers'; -          }, -        }) -        .toCompileTo('helpers'); - -      expectTemplate('{{lookup}}') -        .withInput({ lookup: 'Explicit' }) -        .withHelpers({ -          lookup() { -            return 'helpers'; -          }, -        }) -        .toCompileTo('helpers'); -    }); - -    it('the helpers hash is available is nested contexts', () => { -      expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}') -        .withInput({ outer: { inner: { unused: [] } } }) -        .withHelpers({ -          helper() { -            return 'helper'; -          }, -        }) -        .toCompileTo('helper'); -    }); - -    it('the helper hash should augment the global hash', () => { -      kbnHandlebarsEnv!.registerHelper('test_helper', function () { -        return 'found it!'; -      }); - -      expectTemplate('{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') -        .withInput({ cruel: 'cruel' }) -        .withHelpers({ -          world() { -            return 'world!'; -          }, -        }) -        .toCompileTo('found it! Goodbye cruel world!!'); -    }); -  }); - -  describe('registration', () => { -    it('unregisters', () => { -      deleteAllKeys(kbnHandlebarsEnv!.helpers); - -      kbnHandlebarsEnv!.registerHelper('foo', function () { -        return 'fail'; -      }); -      expect(kbnHandlebarsEnv!.helpers.foo).toBeDefined(); -      kbnHandlebarsEnv!.unregisterHelper('foo'); -      expect(kbnHandlebarsEnv!.helpers.foo).toBeUndefined(); -    }); - -    it('allows multiple globals', () => { -      const ifHelper = kbnHandlebarsEnv!.helpers.if; -      deleteAllKeys(kbnHandlebarsEnv!.helpers); - -      kbnHandlebarsEnv!.registerHelper({ -        if: ifHelper, -        world() { -          return 'world!'; -        }, -        testHelper() { -          return 'found it!'; -        }, -      }); - -      expectTemplate('{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') -        .withInput({ cruel: 'cruel' }) -        .toCompileTo('found it! Goodbye cruel world!!'); -    }); - -    it('fails with multiple and args', () => { -      expect(() => { -        kbnHandlebarsEnv!.registerHelper( -          // @ts-expect-error TypeScript is complaining about the invalid input just as the thrown error -          { -            world() { -              return 'world!'; -            }, -            testHelper() { -              return 'found it!'; -            }, -          }, -          {} -        ); -      }).toThrow('Arg not supported with multiple helpers'); -    }); -  }); - -  it('decimal number literals work', () => { -    expectTemplate('Message: {{hello -1.2 1.2}}') -      .withHelper('hello', function (times, times2) { -        if (typeof times !== 'number') { -          times = 'NaN'; -        } -        if (typeof times2 !== 'number') { -          times2 = 'NaN'; -        } -        return 'Hello ' + times + ' ' + times2 + ' times'; -      }) -      .toCompileTo('Message: Hello -1.2 1.2 times'); -  }); - -  it('negative number literals work', () => { -    expectTemplate('Message: {{hello -12}}') -      .withHelper('hello', function (times) { -        if (typeof times !== 'number') { -          times = 'NaN'; -        } -        return 'Hello ' + times + ' times'; -      }) -      .toCompileTo('Message: Hello -12 times'); -  }); - -  describe('String literal parameters', () => { -    it('simple literals work', () => { -      expectTemplate('Message: {{hello "world" 12 true false}}') -        .withHelper('hello', function (param, times, bool1, bool2) { -          if (typeof times !== 'number') { -            times = 'NaN'; -          } -          if (typeof bool1 !== 'boolean') { -            bool1 = 'NaB'; -          } -          if (typeof bool2 !== 'boolean') { -            bool2 = 'NaB'; -          } -          return 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2; -        }) -        .toCompileTo('Message: Hello world 12 times: true false'); -    }); - -    it('using a quote in the middle of a parameter raises an error', () => { -      expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error); -    }); - -    it('escaping a String is possible', () => { -      expectTemplate('Message: {{{hello "\\"world\\""}}}') -        .withHelper('hello', function (param) { -          return 'Hello ' + param; -        }) -        .toCompileTo('Message: Hello "world"'); -    }); - -    it("it works with ' marks", () => { -      expectTemplate('Message: {{{hello "Alan\'s world"}}}') -        .withHelper('hello', function (param) { -          return 'Hello ' + param; -        }) -        .toCompileTo("Message: Hello Alan's world"); -    }); -  }); - -  describe('multiple parameters', () => { -    it('simple multi-params work', () => { -      expectTemplate('Message: {{goodbye cruel world}}') -        .withInput({ cruel: 'cruel', world: 'world' }) -        .withHelper('goodbye', function (cruel, world) { -          return 'Goodbye ' + cruel + ' ' + world; -        }) -        .toCompileTo('Message: Goodbye cruel world'); -    }); - -    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: HelperOptions) { -          return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world }); -        }) -        .toCompileTo('Message: Goodbye cruel world'); -    }); -  }); - -  describe('hash', () => { -    it('helpers can take an optional hash', () => { -      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}') -        .withHelper('goodbye', function (options: HelperOptions) { -          return ( -            'GOODBYE ' + -            options.hash.cruel + -            ' ' + -            options.hash.world + -            ' ' + -            options.hash.times + -            ' TIMES' -          ); -        }) -        .toCompileTo('GOODBYE CRUEL WORLD 12 TIMES'); -    }); - -    it('helpers can take an optional hash with booleans', () => { -      function goodbye(options: HelperOptions) { -        if (options.hash.print === true) { -          return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world; -        } else if (options.hash.print === false) { -          return 'NOT PRINTING'; -        } else { -          return 'THIS SHOULD NOT HAPPEN'; -        } -      } - -      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}') -        .withHelper('goodbye', goodbye) -        .toCompileTo('GOODBYE CRUEL WORLD'); - -      expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=false}}') -        .withHelper('goodbye', goodbye) -        .toCompileTo('NOT PRINTING'); -    }); - -    it('block helpers can take an optional hash', () => { -      expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') -        .withHelper('goodbye', function (this: any, options: HelperOptions) { -          return ( -            'GOODBYE ' + -            options.hash.cruel + -            ' ' + -            options.fn(this) + -            ' ' + -            options.hash.times + -            ' TIMES' -          ); -        }) -        .toCompileTo('GOODBYE CRUEL world 12 TIMES'); -    }); - -    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: HelperOptions) { -          return ( -            'GOODBYE ' + -            options.hash.cruel + -            ' ' + -            options.fn(this) + -            ' ' + -            options.hash.times + -            ' TIMES' -          ); -        }) -        .toCompileTo('GOODBYE CRUEL world 12 TIMES'); -    }); - -    it('block helpers can take an optional hash with booleans', () => { -      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) { -          return 'NOT PRINTING'; -        } else { -          return 'THIS SHOULD NOT HAPPEN'; -        } -      } - -      expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}') -        .withHelper('goodbye', goodbye) -        .toCompileTo('GOODBYE CRUEL world'); - -      expectTemplate('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}') -        .withHelper('goodbye', goodbye) -        .toCompileTo('NOT PRINTING'); -    }); -  }); - -  describe('helperMissing', () => { -    it('if a context is not found, helperMissing is used', () => { -      expectTemplate('{{hello}} {{link_to world}}').toThrow(/Missing helper: "link_to"/); -    }); - -    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: HelperOptions) { -          if (options.name === 'link_to') { -            return new Handlebars.SafeString('<a>' + mesg + '</a>'); -          } -        }) -        .toCompileTo('Hello <a>world</a>'); -    }); - -    it('if a value is not found, custom helperMissing is used', () => { -      expectTemplate('{{hello}} {{link_to}}') -        .withInput({ hello: 'Hello', world: 'world' }) -        .withHelper('helperMissing', function (options: HelperOptions) { -          if (options.name === 'link_to') { -            return new Handlebars.SafeString('<a>winning</a>'); -          } -        }) -        .toCompileTo('Hello <a>winning</a>'); -    }); -  }); - -  describe('knownHelpers', () => { -    it('Known helper should render helper', () => { -      expectTemplate('{{hello}}') -        .withCompileOptions({ -          knownHelpers: { hello: true }, -        }) -        .withHelper('hello', function () { -          return 'foo'; -        }) -        .toCompileTo('foo'); -    }); - -    it('Unknown helper in knownHelpers only mode should be passed as undefined', () => { -      expectTemplate('{{typeof hello}}') -        .withCompileOptions({ -          knownHelpers: { typeof: true }, -          knownHelpersOnly: true, -        }) -        .withHelper('typeof', function (arg) { -          return typeof arg; -        }) -        .withHelper('hello', function () { -          return 'foo'; -        }) -        .toCompileTo('undefined'); -    }); - -    it('Builtin helpers available in knownHelpers only mode', () => { -      expectTemplate('{{#unless foo}}bar{{/unless}}') -        .withCompileOptions({ -          knownHelpersOnly: true, -        }) -        .toCompileTo('bar'); -    }); - -    it('Field lookup works in knownHelpers only mode', () => { -      expectTemplate('{{foo}}') -        .withCompileOptions({ -          knownHelpersOnly: true, -        }) -        .withInput({ foo: 'bar' }) -        .toCompileTo('bar'); -    }); - -    it('Conditional blocks work in knownHelpers only mode', () => { -      expectTemplate('{{#foo}}bar{{/foo}}') -        .withCompileOptions({ -          knownHelpersOnly: true, -        }) -        .withInput({ foo: 'baz' }) -        .toCompileTo('bar'); -    }); - -    it('Invert blocks work in knownHelpers only mode', () => { -      expectTemplate('{{^foo}}bar{{/foo}}') -        .withCompileOptions({ -          knownHelpersOnly: true, -        }) -        .withInput({ foo: false }) -        .toCompileTo('bar'); -    }); - -    it('Functions are bound to the context in knownHelpers only mode', () => { -      expectTemplate('{{foo}}') -        .withCompileOptions({ -          knownHelpersOnly: true, -        }) -        .withInput({ -          foo() { -            return this.bar; -          }, -          bar: 'bar', -        }) -        .toCompileTo('bar'); -    }); - -    it('Unknown helper call in knownHelpers only mode should throw', () => { -      expectTemplate('{{typeof hello}}') -        .withCompileOptions({ knownHelpersOnly: true }) -        .toThrow(Error); -    }); -  }); - -  describe('blockHelperMissing', () => { -    it('lambdas are resolved by blockHelperMissing, not handlebars proper', () => { -      expectTemplate('{{#truthy}}yep{{/truthy}}') -        .withInput({ -          truthy() { -            return true; -          }, -        }) -        .toCompileTo('yep'); -    }); - -    it('lambdas resolved by blockHelperMissing are bound to the context', () => { -      expectTemplate('{{#truthy}}yep{{/truthy}}') -        .withInput({ -          truthy() { -            return this.truthiness(); -          }, -          truthiness() { -            return false; -          }, -        }) -        .toCompileTo(''); -    }); -  }); - -  describe('name field', () => { -    const helpers = { -      blockHelperMissing(...args: any[]) { -        return 'missing: ' + args[args.length - 1].name; -      }, -      helperMissing(...args: any[]) { -        return 'helper missing: ' + args[args.length - 1].name; -      }, -      helper(...args: any[]) { -        return 'ran: ' + args[args.length - 1].name; -      }, -    }; - -    it('should include in ambiguous mustache calls', () => { -      expectTemplate('{{helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -    }); - -    it('should include in helper mustache calls', () => { -      expectTemplate('{{helper 1}}').withHelpers(helpers).toCompileTo('ran: helper'); -    }); - -    it('should include in ambiguous block calls', () => { -      expectTemplate('{{#helper}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -    }); - -    it('should include in simple block calls', () => { -      expectTemplate('{{#./helper}}{{/./helper}}') -        .withHelpers(helpers) -        .toCompileTo('missing: ./helper'); -    }); - -    it('should include in helper block calls', () => { -      expectTemplate('{{#helper 1}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -    }); - -    it('should include in known helper calls', () => { -      expectTemplate('{{helper}}') -        .withCompileOptions({ -          knownHelpers: { helper: true }, -          knownHelpersOnly: true, -        }) -        .withHelpers(helpers) -        .toCompileTo('ran: helper'); -    }); - -    it('should include full id', () => { -      expectTemplate('{{#foo.helper}}{{/foo.helper}}') -        .withInput({ foo: {} }) -        .withHelpers(helpers) -        .toCompileTo('missing: foo.helper'); -    }); - -    it('should include full id if a hash is passed', () => { -      expectTemplate('{{#foo.helper bar=baz}}{{/foo.helper}}') -        .withInput({ foo: {} }) -        .withHelpers(helpers) -        .toCompileTo('helper missing: foo.helper'); -    }); -  }); - -  describe('name conflicts', () => { -    it('helpers take precedence over same-named context properties', () => { -      expectTemplate('{{goodbye}} {{cruel world}}') -        .withHelper('goodbye', function (this: any) { -          return this.goodbye.toUpperCase(); -        }) -        .withHelper('cruel', function (world) { -          return 'cruel ' + world.toUpperCase(); -        }) -        .withInput({ -          goodbye: 'goodbye', -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel WORLD'); -    }); - -    it('helpers take precedence over same-named context properties$', () => { -      expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}') -        .withHelper('goodbye', function (this: any, options: HelperOptions) { -          return this.goodbye.toUpperCase() + options.fn(this); -        }) -        .withHelper('cruel', function (world) { -          return 'cruel ' + world.toUpperCase(); -        }) -        .withInput({ -          goodbye: 'goodbye', -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel WORLD'); -    }); - -    it('Scoped names take precedence over helpers', () => { -      expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}') -        .withHelper('goodbye', function (this: any) { -          return this.goodbye.toUpperCase(); -        }) -        .withHelper('cruel', function (world) { -          return 'cruel ' + world.toUpperCase(); -        }) -        .withInput({ -          goodbye: 'goodbye', -          world: 'world', -        }) -        .toCompileTo('goodbye cruel WORLD cruel GOODBYE'); -    }); - -    it('Scoped names take precedence over block helpers', () => { -      expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}') -        .withHelper('goodbye', function (this: any, options: HelperOptions) { -          return this.goodbye.toUpperCase() + options.fn(this); -        }) -        .withHelper('cruel', function (world) { -          return 'cruel ' + world.toUpperCase(); -        }) -        .withInput({ -          goodbye: 'goodbye', -          world: 'world', -        }) -        .toCompileTo('GOODBYE cruel WORLD goodbye'); -    }); -  }); - -  describe('block params', () => { -    it('should take presedence over context values', () => { -      expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') -        .withInput({ value: 'foo' }) -        .withHelper('goodbyes', function (options: HelperOptions) { -          expect(options.fn.blockParams).toEqual(1); -          return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); -        }) -        .toCompileTo('1foo'); -    }); - -    it('should take presedence over helper values', () => { -      expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') -        .withHelper('value', function () { -          return 'foo'; -        }) -        .withHelper('goodbyes', function (options: HelperOptions) { -          expect(options.fn.blockParams).toEqual(1); -          return options.fn({}, { blockParams: [1, 2] }); -        }) -        .toCompileTo('1foo'); -    }); - -    it('should not take presedence over pathed values', () => { -      expectTemplate('{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}') -        .withInput({ value: 'bar' }) -        .withHelper('value', function () { -          return 'foo'; -        }) -        .withHelper('goodbyes', function (this: any, options: HelperOptions) { -          expect(options.fn.blockParams).toEqual(1); -          return options.fn(this, { blockParams: [1, 2] }); -        }) -        .toCompileTo('barfoo'); -    }); - -    it('should take presednece over parent block params', () => { -      let value: number; -      expectTemplate( -        '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', -        { -          beforeEach() { -            value = 1; -          }, -        } -      ) -        .withInput({ value: 'foo' }) -        .withHelper('goodbyes', function (options: HelperOptions) { -          return options.fn( -            { value: 'bar' }, -            { -              blockParams: options.fn.blockParams === 1 ? [value++, value++] : undefined, -            } -          ); -        }) -        .toCompileTo('13foo'); -    }); - -    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: HelperOptions) { -          expect(options.fn.blockParams).toEqual(1); -          return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); -        }) -        .toCompileTo('1foo'); -    }); -  }); - -  describe('built-in helpers malformed arguments ', () => { -    it('if helper - too few arguments', () => { -      expectTemplate('{{#if}}{{/if}}').toThrow(/#if requires exactly one argument/); -    }); - -    it('if helper - too many arguments, string', () => { -      expectTemplate('{{#if test "string"}}{{/if}}').toThrow(/#if requires exactly one argument/); -    }); - -    it('if helper - too many arguments, undefined', () => { -      expectTemplate('{{#if test undefined}}{{/if}}').toThrow(/#if requires exactly one argument/); -    }); - -    it('if helper - too many arguments, null', () => { -      expectTemplate('{{#if test null}}{{/if}}').toThrow(/#if requires exactly one argument/); -    }); - -    it('unless helper - too few arguments', () => { -      expectTemplate('{{#unless}}{{/unless}}').toThrow(/#unless requires exactly one argument/); -    }); - -    it('unless helper - too many arguments', () => { -      expectTemplate('{{#unless test null}}{{/unless}}').toThrow( -        /#unless requires exactly one argument/ -      ); -    }); - -    it('with helper - too few arguments', () => { -      expectTemplate('{{#with}}{{/with}}').toThrow(/#with requires exactly one argument/); -    }); - -    it('with helper - too many arguments', () => { -      expectTemplate('{{#with test "string"}}{{/with}}').toThrow( -        /#with requires exactly one argument/ -      ); -    }); -  }); - -  describe('the lookupProperty-option', () => { -    it('should be passed to custom helpers', () => { -      expectTemplate('{{testHelper}}') -        .withHelper('testHelper', function testHelper(this: any, options: HelperOptions) { -          return options.lookupProperty(this, 'testProperty'); -        }) -        .withInput({ testProperty: 'abc' }) -        .toCompileTo('abc'); -    }); -  }); -}); - -function deleteAllKeys(obj: { [key: string]: any }) { -  for (const key of Object.keys(obj)) { -    delete obj[key]; -  } -} diff --git a/dev/lib/handlebars/src/spec/index.partials.test.ts b/dev/lib/handlebars/src/spec/index.partials.test.ts deleted file mode 100644 index 65930d06..00000000 --- a/dev/lib/handlebars/src/spec/index.partials.test.ts +++ /dev/null @@ -1,591 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from '../..'; -import { expectTemplate, forEachCompileFunctionName } from '../__jest__/test_bench'; - -describe('partials', () => { -  it('basic partials', () => { -    const string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; -    const partial = '{{name}} ({{url}}) '; -    const hash = { -      dudes: [ -        { name: 'Yehuda', url: 'http://yehuda' }, -        { name: 'Alan', url: 'http://alan' }, -      ], -    }; - -    expectTemplate(string) -      .withInput(hash) -      .withPartials({ dude: partial }) -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); - -    expectTemplate(string) -      .withInput(hash) -      .withPartials({ dude: partial }) -      .withRuntimeOptions({ data: false }) -      .withCompileOptions({ data: false }) -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); -  }); - -  it('dynamic partials', () => { -    const string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; -    const partial = '{{name}} ({{url}}) '; -    const hash = { -      dudes: [ -        { name: 'Yehuda', url: 'http://yehuda' }, -        { name: 'Alan', url: 'http://alan' }, -      ], -    }; -    const helpers = { -      partial: () => 'dude', -    }; - -    expectTemplate(string) -      .withInput(hash) -      .withHelpers(helpers) -      .withPartials({ dude: partial }) -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); - -    expectTemplate(string) -      .withInput(hash) -      .withHelpers(helpers) -      .withPartials({ dude: partial }) -      .withRuntimeOptions({ data: false }) -      .withCompileOptions({ data: false }) -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); -  }); - -  it('failing dynamic partials', () => { -    expectTemplate('Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}') -      .withInput({ -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withHelper('partial', () => 'missing') -      .withPartial('dude', '{{name}} ({{url}}) ') -      .toThrow('The partial missing could not be found'); // TODO: Is there a way we can test that the error is of type `Handlebars.Exception`? -  }); - -  it('partials with context', () => { -    expectTemplate('Dudes: {{>dude dudes}}') -      .withInput({ -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withPartial('dude', '{{#this}}{{name}} ({{url}}) {{/this}}') -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); -  }); - -  it('partials with no context', () => { -    const partial = '{{name}} ({{url}}) '; -    const hash = { -      dudes: [ -        { name: 'Yehuda', url: 'http://yehuda' }, -        { name: 'Alan', url: 'http://alan' }, -      ], -    }; - -    expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') -      .withInput(hash) -      .withPartial('dude', partial) -      .withCompileOptions({ explicitPartialContext: true }) -      .toCompileTo('Dudes:  ()  () '); - -    expectTemplate('Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}') -      .withInput(hash) -      .withPartial('dude', partial) -      .withCompileOptions({ explicitPartialContext: true }) -      .toCompileTo('Dudes: foo () foo () '); -  }); - -  it('partials with string context', () => { -    expectTemplate('Dudes: {{>dude "dudes"}}') -      .withPartial('dude', '{{.}}') -      .toCompileTo('Dudes: dudes'); -  }); - -  it('partials with undefined context', () => { -    expectTemplate('Dudes: {{>dude dudes}}') -      .withPartial('dude', '{{foo}} Empty') -      .toCompileTo('Dudes:  Empty'); -  }); - -  it('partials with duplicate parameters', () => { -    expectTemplate('Dudes: {{>dude dudes foo bar=baz}}').toThrow( -      'Unsupported number of partial arguments: 2 - 1:7' -    ); -  }); - -  it('partials with parameters', () => { -    expectTemplate('Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}') -      .withInput({ -        foo: 'bar', -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withPartial('dude', '{{others.foo}}{{name}} ({{url}}) ') -      .toCompileTo('Dudes: barYehuda (http://yehuda) barAlan (http://alan) '); -  }); - -  it('partial in a partial', () => { -    expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') -      .withInput({ -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withPartials({ -        dude: '{{name}} {{> url}} ', -        url: '<a href="{{url}}">{{url}}</a>', -      }) -      .toCompileTo( -        'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ' -      ); -  }); - -  it('rendering undefined partial throws an exception', () => { -    expectTemplate('{{> whatever}}').toThrow('The partial whatever could not be found'); -  }); - -  it('registering undefined partial throws an exception', () => { -    global.kbnHandlebarsEnv = Handlebars.create(); - -    expect(() => { -      kbnHandlebarsEnv!.registerPartial('undefined_test', undefined as any); -    }).toThrow('Attempting to register a partial called "undefined_test" as undefined'); - -    global.kbnHandlebarsEnv = null; -  }); - -  it('rendering template partial in vm mode throws an exception', () => { -    expectTemplate('{{> whatever}}').toThrow('The partial whatever could not be found'); -  }); - -  it('rendering function partial in vm mode', () => { -    function partial(context: any) { -      return context.name + ' (' + context.url + ') '; -    } -    expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') -      .withInput({ -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withPartial('dude', partial) -      .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); -  }); - -  it('GH-14: a partial preceding a selector', () => { -    expectTemplate('Dudes: {{>dude}} {{anotherDude}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('dude', '{{name}}') -      .toCompileTo('Dudes: Jeepers Creepers'); -  }); - -  it('Partials with slash paths', () => { -    expectTemplate('Dudes: {{> shared/dude}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('shared/dude', '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('Partials with slash and point paths', () => { -    expectTemplate('Dudes: {{> shared/dude.thing}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('shared/dude.thing', '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('Global Partials', () => { -    global.kbnHandlebarsEnv = Handlebars.create(); - -    kbnHandlebarsEnv!.registerPartial('globalTest', '{{anotherDude}}'); - -    expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('shared/dude', '{{name}}') -      .toCompileTo('Dudes: Jeepers Creepers'); - -    kbnHandlebarsEnv!.unregisterPartial('globalTest'); -    expect(kbnHandlebarsEnv!.partials.globalTest).toBeUndefined(); - -    global.kbnHandlebarsEnv = null; -  }); - -  it('Multiple partial registration', () => { -    global.kbnHandlebarsEnv = Handlebars.create(); - -    kbnHandlebarsEnv!.registerPartial({ -      'shared/dude': '{{name}}', -      globalTest: '{{anotherDude}}', -    }); - -    expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('notused', 'notused') // trick the test bench into running with partials enabled -      .toCompileTo('Dudes: Jeepers Creepers'); - -    global.kbnHandlebarsEnv = null; -  }); - -  it('Partials with integer path', () => { -    expectTemplate('Dudes: {{> 404}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial(404, '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('Partials with complex path', () => { -    expectTemplate('Dudes: {{> 404/asdf?.bar}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('404/asdf?.bar', '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('Partials with escaped', () => { -    expectTemplate('Dudes: {{> [+404/asdf?.bar]}}') -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('+404/asdf?.bar', '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('Partials with string', () => { -    expectTemplate("Dudes: {{> '+404/asdf?.bar'}}") -      .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) -      .withPartial('+404/asdf?.bar', '{{name}}') -      .toCompileTo('Dudes: Jeepers'); -  }); - -  it('should handle empty partial', () => { -    expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') -      .withInput({ -        dudes: [ -          { name: 'Yehuda', url: 'http://yehuda' }, -          { name: 'Alan', url: 'http://alan' }, -        ], -      }) -      .withPartial('dude', '') -      .toCompileTo('Dudes: '); -  }); - -  // Skipping test as this only makes sense when there's no `compile` function (i.e. runtime-only mode). -  // We do not support that mode with `@kbn/handlebars`, so there's no need to test it -  it.skip('throw on missing partial', () => { -    const handlebars = Handlebars.create(); -    (handlebars.compile as any) = undefined; -    const template = handlebars.precompile('{{> dude}}'); -    const render = handlebars.template(eval('(' + template + ')')); // eslint-disable-line no-eval -    expect(() => { -      render( -        {}, -        { -          partials: { -            dude: 'fail', -          }, -        } -      ); -    }).toThrow(/The partial dude could not be compiled/); -  }); - -  describe('partial blocks', () => { -    it('should render partial block as default', () => { -      expectTemplate('{{#> dude}}success{{/dude}}').toCompileTo('success'); -    }); - -    it('should execute default block with proper context', () => { -      expectTemplate('{{#> dude context}}{{value}}{{/dude}}') -        .withInput({ context: { value: 'success' } }) -        .toCompileTo('success'); -    }); - -    it('should propagate block parameters to default block', () => { -      expectTemplate('{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}') -        .withInput({ context: { value: 'success' } }) -        .toCompileTo('success'); -    }); - -    it('should not use partial block if partial exists', () => { -      expectTemplate('{{#> dude}}fail{{/dude}}') -        .withPartials({ dude: 'success' }) -        .toCompileTo('success'); -    }); - -    it('should render block from partial', () => { -      expectTemplate('{{#> dude}}success{{/dude}}') -        .withPartials({ dude: '{{> @partial-block }}' }) -        .toCompileTo('success'); -    }); - -    it('should be able to render the partial-block twice', () => { -      expectTemplate('{{#> dude}}success{{/dude}}') -        .withPartials({ dude: '{{> @partial-block }} {{> @partial-block }}' }) -        .toCompileTo('success success'); -    }); - -    it('should render block from partial with context', () => { -      expectTemplate('{{#> dude}}{{value}}{{/dude}}') -        .withInput({ context: { value: 'success' } }) -        .withPartials({ -          dude: '{{#with context}}{{> @partial-block }}{{/with}}', -        }) -        .toCompileTo('success'); -    }); - -    it('should be able to access the @data frame from a partial-block', () => { -      expectTemplate('{{#> dude}}in-block: {{@root/value}}{{/dude}}') -        .withInput({ value: 'success' }) -        .withPartials({ -          dude: '<code>before-block: {{@root/value}} {{>   @partial-block }}</code>', -        }) -        .toCompileTo('<code>before-block: success in-block: success</code>'); -    }); - -    it('should allow the #each-helper to be used along with partial-blocks', () => { -      expectTemplate('<template>{{#> list value}}value = {{.}}{{/list}}</template>') -        .withInput({ -          value: ['a', 'b', 'c'], -        }) -        .withPartials({ -          list: '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>', -        }) -        .toCompileTo( -          '<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>' -        ); -    }); - -    it('should render block from partial with context (twice)', () => { -      expectTemplate('{{#> dude}}{{value}}{{/dude}}') -        .withInput({ context: { value: 'success' } }) -        .withPartials({ -          dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}', -        }) -        .toCompileTo('success success'); -    }); - -    it('should render block from partial with context [2]', () => { -      expectTemplate('{{#> dude}}{{../context/value}}{{/dude}}') -        .withInput({ context: { value: 'success' } }) -        .withPartials({ -          dude: '{{#with context}}{{> @partial-block }}{{/with}}', -        }) -        .toCompileTo('success'); -    }); - -    it('should render block from partial with block params', () => { -      expectTemplate('{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}') -        .withInput({ context: { value: 'success' } }) -        .withPartials({ dude: '{{> @partial-block }}' }) -        .toCompileTo('success'); -    }); - -    it('should render nested partial blocks', () => { -      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>') -        .withInput({ value: 'success' }) -        .withPartials({ -          outer: -            '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>', -          nested: '<nested>{{> @partial-block}}</nested>', -        }) -        .toCompileTo( -          '<template><outer><nested><outer-block>success</outer-block></nested></outer></template>' -        ); -    }); - -    it('should render nested partial blocks at different nesting levels', () => { -      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>') -        .withInput({ value: 'success' }) -        .withPartials({ -          outer: -            '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>', -          nested: '<nested>{{> @partial-block}}</nested>', -        }) -        .toCompileTo( -          '<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>' -        ); -    }); - -    it('should render nested partial blocks at different nesting levels (twice)', () => { -      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>') -        .withInput({ value: 'success' }) -        .withPartials({ -          outer: -            '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>', -          nested: '<nested>{{> @partial-block}}</nested>', -        }) -        .toCompileTo( -          '<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>' -        ); -    }); - -    it('should render nested partial blocks (twice at each level)', () => { -      expectTemplate('<template>{{#> outer}}{{value}}{{/outer}}</template>') -        .withInput({ value: 'success' }) -        .withPartials({ -          outer: -            '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>', -          nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>', -        }) -        .toCompileTo( -          '<template><outer>' + -            '<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' + -            '</outer></template>' -        ); -    }); -  }); - -  describe('inline partials', () => { -    it('should define inline partials for template', () => { -      expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}').toCompileTo( -        'success' -      ); -    }); - -    it('should overwrite multiple partials in the same template', () => { -      expectTemplate( -        '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' -      ).toCompileTo('success'); -    }); - -    it('should define inline partials for block', () => { -      expectTemplate( -        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' -      ).toCompileTo('success'); - -      expectTemplate( -        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}' -      ).toThrow(/myPartial could not/); -    }); - -    it('should override global partials', () => { -      expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}') -        .withPartials({ -          myPartial: () => 'fail', -        }) -        .toCompileTo('success'); -    }); - -    it('should override template partials', () => { -      expectTemplate( -        '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' -      ).toCompileTo('success'); -    }); - -    it('should override partials down the entire stack', () => { -      expectTemplate( -        '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}' -      ).toCompileTo('success'); -    }); - -    it('should define inline partials for partial call', () => { -      expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> dude}}') -        .withPartials({ dude: '{{> myPartial }}' }) -        .toCompileTo('success'); -    }); - -    it('should define inline partials in partial block call', () => { -      expectTemplate('{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}') -        .withPartials({ dude: '{{> myPartial }}' }) -        .toCompileTo('success'); -    }); - -    it('should render nested inline partials', () => { -      expectTemplate( -        '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' + -          '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' + -          '{{#>outer}}{{value}}{{/outer}}' -      ) -        .withInput({ value: 'success' }) -        .toCompileTo('<inner><outer-block>success</outer-block></inner>'); -    }); - -    it('should render nested inline partials with partial-blocks on different nesting levels', () => { -      expectTemplate( -        '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' + -          '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' + -          '{{#>outer}}{{value}}{{/outer}}' -      ) -        .withInput({ value: 'success' }) -        .toCompileTo('<inner><outer-block>success</outer-block></inner>success'); -    }); - -    it('should render nested inline partials (twice at each level)', () => { -      expectTemplate( -        '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' + -          '{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' + -          '{{#>outer}}{{value}}{{/outer}}' -      ) -        .withInput({ value: 'success' }) -        .toCompileTo( -          '<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>' -        ); -    }); -  }); - -  forEachCompileFunctionName((compileName) => { -    it(`should pass compiler flags for ${compileName} function`, () => { -      const env = Handlebars.create(); -      env.registerPartial('partial', '{{foo}}'); -      const compile = env[compileName].bind(env); -      const template = compile('{{foo}} {{> partial}}', { noEscape: true }); -      expect(template({ foo: '<' })).toEqual('< <'); -    }); -  }); - -  describe('standalone partials', () => { -    it('indented partials', () => { -      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}') -        .withInput({ -          dudes: [ -            { name: 'Yehuda', url: 'http://yehuda' }, -            { name: 'Alan', url: 'http://alan' }, -          ], -        }) -        .withPartial('dude', '{{name}}\n') -        .toCompileTo('Dudes:\n  Yehuda\n  Alan\n'); -    }); - -    it('nested indented partials', () => { -      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}') -        .withInput({ -          dudes: [ -            { name: 'Yehuda', url: 'http://yehuda' }, -            { name: 'Alan', url: 'http://alan' }, -          ], -        }) -        .withPartials({ -          dude: '{{name}}\n {{> url}}', -          url: '{{url}}!\n', -        }) -        .toCompileTo('Dudes:\n  Yehuda\n   http://yehuda!\n  Alan\n   http://alan!\n'); -    }); - -    it('prevent nested indented partials', () => { -      expectTemplate('Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}') -        .withInput({ -          dudes: [ -            { name: 'Yehuda', url: 'http://yehuda' }, -            { name: 'Alan', url: 'http://alan' }, -          ], -        }) -        .withPartials({ -          dude: '{{name}}\n {{> url}}', -          url: '{{url}}!\n', -        }) -        .withCompileOptions({ preventIndent: true }) -        .toCompileTo('Dudes:\n  Yehuda\n http://yehuda!\n  Alan\n http://alan!\n'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.regressions.test.ts b/dev/lib/handlebars/src/spec/index.regressions.test.ts deleted file mode 100644 index fc2065fe..00000000 --- a/dev/lib/handlebars/src/spec/index.regressions.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars, { type HelperOptions } from '../..'; -import { expectTemplate, forEachCompileFunctionName } from '../__jest__/test_bench'; - -describe('Regressions', () => { -  it('GH-94: Cannot read property of undefined', () => { -    expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}') -      .withInput({ -        books: [ -          { -            title: 'The origin of species', -            author: { -              name: 'Charles Darwin', -            }, -          }, -          { -            title: 'Lazarillo de Tormes', -          }, -        ], -      }) -      .toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes'); -  }); - -  it("GH-150: Inverted sections print when they shouldn't", () => { -    const string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; -    expectTemplate(string).toCompileTo('not set :: '); -    expectTemplate(string).withInput({ set: undefined }).toCompileTo('not set :: '); -    expectTemplate(string).withInput({ set: false }).toCompileTo('not set :: '); -    expectTemplate(string).withInput({ set: true }).toCompileTo(' :: set'); -  }); - -  it('GH-158: Using array index twice, breaks the template', () => { -    expectTemplate('{{arr.[0]}}, {{arr.[1]}}') -      .withInput({ arr: [1, 2] }) -      .toCompileTo('1, 2'); -  }); - -  it("bug reported by @fat where lambdas weren't being properly resolved", () => { -    const string = -      '<strong>This is a slightly more complicated {{thing}}.</strong>.\n' + -      '{{! Just ignore this business. }}\n' + -      'Check this out:\n' + -      '{{#hasThings}}\n' + -      '<ul>\n' + -      '{{#things}}\n' + -      '<li class={{className}}>{{word}}</li>\n' + -      '{{/things}}</ul>.\n' + -      '{{/hasThings}}\n' + -      '{{^hasThings}}\n' + -      '\n' + -      '<small>Nothing to check out...</small>\n' + -      '{{/hasThings}}'; - -    const data = { -      thing() { -        return 'blah'; -      }, -      things: [ -        { className: 'one', word: '@fat' }, -        { className: 'two', word: '@dhg' }, -        { className: 'three', word: '@sayrer' }, -      ], -      hasThings() { -        return true; -      }, -    }; - -    const output = -      '<strong>This is a slightly more complicated blah.</strong>.\n' + -      'Check this out:\n' + -      '<ul>\n' + -      '<li class=one>@fat</li>\n' + -      '<li class=two>@dhg</li>\n' + -      '<li class=three>@sayrer</li>\n' + -      '</ul>.\n'; - -    expectTemplate(string).withInput(data).toCompileTo(output); -  }); - -  it('GH-408: Multiple loops fail', () => { -    expectTemplate('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}') -      .withInput([ -        { name: 'John Doe', location: { city: 'Chicago' } }, -        { name: 'Jane Doe', location: { city: 'New York' } }, -      ]) -      .toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe'); -  }); - -  it('GS-428: Nested if else rendering', () => { -    const succeedingTemplate = -      '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}}  {{#blk}} Expected {{/blk}} {{/inverse}}'; -    const failingTemplate = -      '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; - -    const helpers = { -      blk(block: HelperOptions) { -        return block.fn(''); -      }, -      inverse(block: HelperOptions) { -        return block.inverse(''); -      }, -    }; - -    expectTemplate(succeedingTemplate).withHelpers(helpers).toCompileTo('   Expected  '); -    expectTemplate(failingTemplate).withHelpers(helpers).toCompileTo('  Expected  '); -  }); - -  it('GH-458: Scoped this identifier', () => { -    expectTemplate('{{./foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); -  }); - -  it('GH-375: Unicode line terminators', () => { -    expectTemplate('\u2028').toCompileTo('\u2028'); -  }); - -  it('GH-534: Object prototype aliases', () => { -    /* eslint-disable no-extend-native */ -    // @ts-expect-error -    Object.prototype[0xd834] = true; - -    expectTemplate('{{foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); - -    // @ts-expect-error -    delete Object.prototype[0xd834]; -    /* eslint-enable no-extend-native */ -  }); - -  it('GH-437: Matching escaping', () => { -    expectTemplate('{{{a}}').toThrow(/Parse error on/); -    expectTemplate('{{a}}}').toThrow(/Parse error on/); -  }); - -  it('GH-676: Using array in escaping mustache fails', () => { -    const data = { arr: [1, 2] }; -    expectTemplate('{{arr}}').withInput(data).toCompileTo(data.arr.toString()); -  }); - -  it('Mustache man page', () => { -    expectTemplate( -      'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}' -    ) -      .withInput({ -        name: 'Chris', -        value: 10000, -        taxed_value: 10000 - 10000 * 0.4, -        in_ca: true, -      }) -      .toCompileTo('Hello Chris. You have just won $10000! Well, $6000, after taxes.'); -  }); - -  it('GH-731: zero context rendering', () => { -    expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}') -      .withInput({ -        foo: 0, -        bar: 'OK', -      }) -      .toCompileTo(' This is  ~ '); -  }); - -  it('GH-820: zero pathed rendering', () => { -    expectTemplate('{{foo.bar}}').withInput({ foo: 0 }).toCompileTo(''); -  }); - -  it('GH-837: undefined values for helpers', () => { -    expectTemplate('{{str bar.baz}}') -      .withHelpers({ -        str(value) { -          return value + ''; -        }, -      }) -      .toCompileTo('undefined'); -  }); - -  it('GH-926: Depths and de-dupe', () => { -    expectTemplate( -      '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}' -    ) -      .withInput({ -        name: 'foo', -        data: [1], -        notData: [1], -      }) -      .toCompileTo('foo'); -  }); - -  it('GH-1021: Each empty string key', () => { -    expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}') -      .withInput({ -        data: { -          '': 'foo', -          name: 'Chris', -          value: 10000, -        }, -      }) -      .toCompileTo('Key: \nKey: name\nKey: value\n'); -  }); - -  it('GH-1054: Should handle simple safe string responses', () => { -    expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') -      .withHelpers({ -        wrap(options: HelperOptions) { -          return new Handlebars.SafeString(options.fn()); -        }, -      }) -      .withPartials({ -        partial: '{{#wrap}}<partial>{{/wrap}}', -      }) -      .toCompileTo('<partial>'); -  }); - -  it('GH-1065: Sparse arrays', () => { -    const array = []; -    array[1] = 'foo'; -    array[3] = 'bar'; -    expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}') -      .withInput({ array }) -      .toCompileTo('1foo3bar'); -  }); - -  it('GH-1093: Undefined helper context', () => { -    expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}') -      .withInput({ obj: { foo: undefined, bar: 'bat' } }) -      .withHelpers({ -        helper(this: any) { -          // It's valid to execute a block against an undefined context, but -          // helpers can not do so, so we expect to have an empty object here; -          for (const name in this) { -            if (Object.prototype.hasOwnProperty.call(this, name)) { -              return 'found'; -            } -          } -          // And to make IE happy, check for the known string as length is not enumerated. -          return this === 'bat' ? 'found' : 'not'; -        }, -      }) -      .toCompileTo('notfoundbat'); -  }); - -  it('should support multiple levels of inline partials', () => { -    expectTemplate('{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}') -      .withPartials({ -        doctype: 'doctype{{> content}}', -        layout: '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}', -      }) -      .toCompileTo('doctypelayoutsubcontent'); -  }); - -  it('GH-1089: should support failover content in multiple levels of inline partials', () => { -    expectTemplate('{{#> layout}}{{/layout}}') -      .withPartials({ -        doctype: 'doctype{{> content}}', -        layout: -          '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}', -      }) -      .toCompileTo('doctypelayoutsubcontent'); -  }); - -  it('GH-1099: should support greater than 3 nested levels of inline partials', () => { -    expectTemplate('{{#> layout}}Outer{{/layout}}') -      .withPartials({ -        layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', -        inner: '', -      }) -      .toCompileTo('Outer'); -  }); - -  it('GH-1135 : Context handling within each iteration', () => { -    expectTemplate( -      '{{#each array}}\n' + -        ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' + -        ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' + -        '{{/each}}' -    ) -      .withInput({ array: [1], name: 'John' }) -      .withHelpers({ -        myif(conditional, options: HelperOptions) { -          if (conditional) { -            return options.fn(this); -          } else { -            return options.inverse(this); -          } -        }, -      }) -      .toCompileTo(' 1. IF: John--\n' + ' 2. MYIF: John==\n'); -  }); - -  it('GH-1186: Support block params for existing programs', () => { -    expectTemplate( -      '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + -        '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + -        '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' -    ) -      .withInput({ -        listOne: ['a'], -        listTwo: ['b'], -      }) -      .toCompileTo('ab'); -  }); - -  it('GH-1319: "unless" breaks when "each" value equals "null"', () => { -    expectTemplate('{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}') -      .withInput({ -        value: 'parent', -        list: [null, 'a'], -      }) -      .toCompileTo('parent=parent parent=parent '); -  }); - -  it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', () => { -    expectTemplate('template {{>partial}} template') -      .withPartials({ -        partialWithBlock: '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', -        partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}', -      }) -      .toCompileTo('template  block  partial  block  template'); -  }); - -  it('should allow hash with protected array names', () => { -    expectTemplate('{{helpa length="foo"}}') -      .withInput({ array: [1], name: 'John' }) -      .withHelpers({ -        helpa(options: HelperOptions) { -          return options.hash.length; -        }, -      }) -      .toCompileTo('foo'); -  }); - -  describe('GH-1598: Performance degradation for partials since v4.3.0', () => { -    let newHandlebarsInstance: typeof Handlebars; -    let spy: jest.SpyInstance; -    beforeEach(() => { -      newHandlebarsInstance = Handlebars.create(); -    }); -    afterEach(() => { -      spy.mockRestore(); -    }); - -    forEachCompileFunctionName((compileName) => { -      it(`should only compile global partials once when calling #${compileName}`, () => { -        const compile = newHandlebarsInstance[compileName].bind(newHandlebarsInstance); -        let calls; -        switch (compileName) { -          case 'compile': -            spy = jest.spyOn(newHandlebarsInstance, 'template'); -            calls = 3; -            break; -          case 'compileAST': -            spy = jest.spyOn(newHandlebarsInstance, 'compileAST'); -            calls = 2; -            break; -        } -        newHandlebarsInstance.registerPartial({ -          dude: 'I am a partial', -        }); -        const string = 'Dudes: {{> dude}} {{> dude}}'; -        compile(string)(); // This should compile template + partial once -        compile(string)(); // This should only compile template -        expect(spy).toHaveBeenCalledTimes(calls); -        spy.mockRestore(); -      }); -    }); -  }); - -  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 as any) -        .withInput({ foo: 'bar' }) -        .toCompileTo('bar'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.security.test.ts b/dev/lib/handlebars/src/spec/index.security.test.ts deleted file mode 100644 index 878a0931..00000000 --- a/dev/lib/handlebars/src/spec/index.security.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('security issues', () => { -  describe('GH-1495: Prevent Remote Code Execution via constructor', () => { -    it('should not allow constructors to be accessed', () => { -      expectTemplate('{{lookup (lookup this "constructor") "name"}}').withInput({}).toCompileTo(''); -      expectTemplate('{{constructor.name}}').withInput({}).toCompileTo(''); -    }); - -    it('GH-1603: should not allow constructors to be accessed (lookup via toString)', () => { -      expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}') -        .withInput({}) -        .withHelper('list', function (element) { -          return [element]; -        }) -        .toCompileTo(''); -    }); - -    it('should allow the "constructor" property to be accessed if it is an "ownProperty"', () => { -      expectTemplate('{{constructor.name}}') -        .withInput({ constructor: { name: 'here we go' } }) -        .toCompileTo('here we go'); - -      expectTemplate('{{lookup (lookup this "constructor") "name"}}') -        .withInput({ constructor: { name: 'here we go' } }) -        .toCompileTo('here we go'); -    }); - -    it('should allow the "constructor" property to be accessed if it is an "own property"', () => { -      expectTemplate('{{lookup (lookup this "constructor") "name"}}') -        .withInput({ constructor: { name: 'here we go' } }) -        .toCompileTo('here we go'); -    }); -  }); - -  describe('GH-1558: Prevent explicit call of helperMissing-helpers', () => { -    describe('without the option "allowExplicitCallOfHelperMissing"', () => { -      it('should throw an exception when calling  "{{helperMissing}}" ', () => { -        expectTemplate('{{helperMissing}}').toThrow(Error); -      }); - -      it('should throw an exception when calling  "{{#helperMissing}}{{/helperMissing}}" ', () => { -        expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error); -      }); - -      it('should throw an exception when calling  "{{blockHelperMissing "abc" .}}" ', () => { -        const functionCalls = []; -        expect(() => { -          const template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); -          template({ -            fn() { -              functionCalls.push('called'); -            }, -          }); -        }).toThrow(Error); -        expect(functionCalls.length).toEqual(0); -      }); - -      it('should throw an exception when calling  "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', () => { -        expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}') -          .withInput({ -            fn() { -              return 'functionInData'; -            }, -          }) -          .toThrow(Error); -      }); -    }); -  }); - -  describe('GH-1563', () => { -    it('should not allow to access constructor after overriding via __defineGetter__', () => { -      // @ts-expect-error -      if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) { -        return; // Browser does not support this exploit anyway -      } -      expectTemplate( -        '{{__defineGetter__ "undefined" valueOf }}' + -          '{{#with __lookupGetter__ }}' + -          '{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' + -          '{{constructor.name}}' + -          '{{/with}}' -      ) -        .withInput({}) -        .toThrow(/Missing helper: "__defineGetter__"/); -    }); -  }); - -  describe('GH-1595: dangerous properties', () => { -    const templates = [ -      '{{constructor}}', -      '{{__defineGetter__}}', -      '{{__defineSetter__}}', -      '{{__lookupGetter__}}', -      '{{__proto__}}', -      '{{lookup this "constructor"}}', -      '{{lookup this "__defineGetter__"}}', -      '{{lookup this "__defineSetter__"}}', -      '{{lookup this "__lookupGetter__"}}', -      '{{lookup this "__proto__"}}', -    ]; - -    templates.forEach((template) => { -      describe('access should be denied to ' + template, () => { -        it('by default', () => { -          expectTemplate(template).withInput({}).toCompileTo(''); -        }); -      }); -    }); -  }); - -  describe('escapes template variables', () => { -    it('in default mode', () => { -      expectTemplate("{{'a\\b'}}").withCompileOptions().withInput({ 'a\\b': 'c' }).toCompileTo('c'); -    }); - -    it('in strict mode', () => { -      expectTemplate("{{'a\\b'}}") -        .withCompileOptions({ strict: true }) -        .withInput({ 'a\\b': 'c' }) -        .toCompileTo('c'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.strict.test.ts b/dev/lib/handlebars/src/spec/index.strict.test.ts deleted file mode 100644 index a8f294b9..00000000 --- a/dev/lib/handlebars/src/spec/index.strict.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import { expectTemplate } from '../__jest__/test_bench'; - -describe('strict', () => { -  describe('strict mode', () => { -    it('should error on missing property lookup', () => { -      expectTemplate('{{hello}}') -        .withCompileOptions({ strict: true }) -        .toThrow(/"hello" not defined in/); -    }); - -    it('should error on missing child', () => { -      expectTemplate('{{hello.bar}}') -        .withCompileOptions({ strict: true }) -        .withInput({ hello: { bar: 'foo' } }) -        .toCompileTo('foo'); - -      expectTemplate('{{hello.bar}}') -        .withCompileOptions({ strict: true }) -        .withInput({ hello: {} }) -        .toThrow(/"bar" not defined in/); -    }); - -    it('should handle explicit undefined', () => { -      expectTemplate('{{hello.bar}}') -        .withCompileOptions({ strict: true }) -        .withInput({ hello: { bar: undefined } }) -        .toCompileTo(''); -    }); - -    it('should error on missing property lookup in known helpers mode', () => { -      expectTemplate('{{hello}}') -        .withCompileOptions({ -          strict: true, -          knownHelpersOnly: true, -        }) -        .toThrow(/"hello" not defined in/); -    }); - -    it('should error on missing context', () => { -      expectTemplate('{{hello}}').withCompileOptions({ strict: true }).toThrow(Error); -    }); - -    it('should error on missing data lookup', () => { -      const xt = expectTemplate('{{@hello}}').withCompileOptions({ -        strict: true, -      }); - -      xt.toThrow(Error); - -      xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo'); -    }); - -    it('should not run helperMissing for helper calls', () => { -      expectTemplate('{{hello foo}}') -        .withCompileOptions({ strict: true }) -        .withInput({ foo: true }) -        .toThrow(/"hello" not defined in/); - -      expectTemplate('{{#hello foo}}{{/hello}}') -        .withCompileOptions({ strict: true }) -        .withInput({ foo: true }) -        .toThrow(/"hello" not defined in/); -    }); - -    it('should throw on ambiguous blocks', () => { -      expectTemplate('{{#hello}}{{/hello}}') -        .withCompileOptions({ strict: true }) -        .toThrow(/"hello" not defined in/); - -      expectTemplate('{{^hello}}{{/hello}}') -        .withCompileOptions({ strict: true }) -        .toThrow(/"hello" not defined in/); - -      expectTemplate('{{#hello.bar}}{{/hello.bar}}') -        .withCompileOptions({ strict: true }) -        .withInput({ hello: {} }) -        .toThrow(/"bar" not defined in/); -    }); - -    it('should allow undefined parameters when passed to helpers', () => { -      expectTemplate('{{#unless foo}}success{{/unless}}') -        .withCompileOptions({ strict: true }) -        .toCompileTo('success'); -    }); - -    it('should allow undefined hash when passed to helpers', () => { -      expectTemplate('{{helper value=@foo}}') -        .withCompileOptions({ -          strict: true, -        }) -        .withHelpers({ -          helper(options) { -            expect('value' in options.hash).toEqual(true); -            expect(options.hash.value).toBeUndefined(); -            return 'success'; -          }, -        }) -        .toCompileTo('success'); -    }); - -    it('should show error location on missing property lookup', () => { -      expectTemplate('\n\n\n   {{hello}}') -        .withCompileOptions({ strict: true }) -        .toThrow('"hello" not defined in [object Object] - 4:5'); -    }); - -    it('should error contains correct location properties on missing property lookup', () => { -      try { -        expectTemplate('\n\n\n   {{hello}}') -          .withCompileOptions({ strict: true }) -          .toCompileTo('throw before asserting this'); -      } catch (error) { -        expect(error.lineNumber).toEqual(4); -        expect(error.endLineNumber).toEqual(4); -        expect(error.column).toEqual(5); -        expect(error.endColumn).toEqual(10); -      } -    }); -  }); - -  describe('assume objects', () => { -    it('should ignore missing property', () => { -      expectTemplate('{{hello}}').withCompileOptions({ assumeObjects: true }).toCompileTo(''); -    }); - -    it('should ignore missing child', () => { -      expectTemplate('{{hello.bar}}') -        .withCompileOptions({ assumeObjects: true }) -        .withInput({ hello: {} }) -        .toCompileTo(''); -    }); - -    it('should error on missing object', () => { -      expectTemplate('{{hello.bar}}').withCompileOptions({ assumeObjects: true }).toThrow(Error); -    }); - -    it('should error on missing context', () => { -      expectTemplate('{{hello}}') -        .withCompileOptions({ assumeObjects: true }) -        .withInput(undefined) -        .toThrow(Error); -    }); - -    it('should error on missing data lookup', () => { -      expectTemplate('{{@hello.bar}}') -        .withCompileOptions({ assumeObjects: true }) -        .withInput(undefined) -        .toThrow(Error); -    }); - -    it('should execute blockHelperMissing', () => { -      expectTemplate('{{^hello}}foo{{/hello}}') -        .withCompileOptions({ assumeObjects: true }) -        .toCompileTo('foo'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.subexpressions.test.ts b/dev/lib/handlebars/src/spec/index.subexpressions.test.ts deleted file mode 100644 index 4dee24b7..00000000 --- a/dev/lib/handlebars/src/spec/index.subexpressions.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars, { type HelperOptions } from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('subexpressions', () => { -  it('arg-less helper', () => { -    expectTemplate('{{foo (bar)}}!') -      .withHelpers({ -        foo(val) { -          return val + val; -        }, -        bar() { -          return 'LOL'; -        }, -      }) -      .toCompileTo('LOLLOL!'); -  }); - -  it('helper w args', () => { -    expectTemplate('{{blog (equal a b)}}') -      .withInput({ bar: 'LOL' }) -      .withHelpers({ -        blog(val) { -          return 'val is ' + val; -        }, -        equal(x, y) { -          return x === y; -        }, -      }) -      .toCompileTo('val is true'); -  }); - -  it('mixed paths and helpers', () => { -    expectTemplate('{{blog baz.bat (equal a b) baz.bar}}') -      .withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }) -      .withHelpers({ -        blog(val, that, theOther) { -          return 'val is ' + val + ', ' + that + ' and ' + theOther; -        }, -        equal(x, y) { -          return x === y; -        }, -      }) -      .toCompileTo('val is foo!, true and bar!'); -  }); - -  it('supports much nesting', () => { -    expectTemplate('{{blog (equal (equal true true) true)}}') -      .withInput({ bar: 'LOL' }) -      .withHelpers({ -        blog(val) { -          return 'val is ' + val; -        }, -        equal(x, y) { -          return x === y; -        }, -      }) -      .toCompileTo('val is true'); -  }); - -  it('GH-800 : Complex subexpressions', () => { -    const context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } }; -    const helpers = { -      dash(a: any, b: any) { -        return a + '-' + b; -      }, -      concat(a: any, b: any) { -        return a + b; -      }, -    }; - -    expectTemplate("{{dash 'abc' (concat a b)}}") -      .withInput(context) -      .withHelpers(helpers) -      .toCompileTo('abc-ab'); - -    expectTemplate('{{dash d (concat a b)}}') -      .withInput(context) -      .withHelpers(helpers) -      .toCompileTo('d-ab'); - -    expectTemplate('{{dash c.c (concat a b)}}') -      .withInput(context) -      .withHelpers(helpers) -      .toCompileTo('c-ab'); - -    expectTemplate('{{dash (concat a b) c.c}}') -      .withInput(context) -      .withHelpers(helpers) -      .toCompileTo('ab-c'); - -    expectTemplate('{{dash (concat a e.e) c.c}}') -      .withInput(context) -      .withHelpers(helpers) -      .toCompileTo('ae-c'); -  }); - -  it('provides each nested helper invocation its own options hash', () => { -    let lastOptions: HelperOptions; -    const helpers = { -      equal(x: any, y: any, options: HelperOptions) { -        if (!options || options === lastOptions) { -          throw new Error('options hash was reused'); -        } -        lastOptions = options; -        return x === y; -      }, -    }; -    expectTemplate('{{equal (equal true true) true}}').withHelpers(helpers).toCompileTo('true'); -  }); - -  it('with hashes', () => { -    expectTemplate("{{blog (equal (equal true true) true fun='yes')}}") -      .withInput({ bar: 'LOL' }) -      .withHelpers({ -        blog(val) { -          return 'val is ' + val; -        }, -        equal(x, y) { -          return x === y; -        }, -      }) -      .toCompileTo('val is true'); -  }); - -  it('as hashes', () => { -    expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}") -      .withHelpers({ -        blog(options) { -          return 'val is ' + options.hash.fun; -        }, -        equal(x, y) { -          return x === y; -        }, -      }) -      .toCompileTo('val is true'); -  }); - -  it('multiple subexpressions in a hash', () => { -    expectTemplate('{{input aria-label=(t "Name") placeholder=(t "Example User")}}') -      .withHelpers({ -        input(options) { -          const hash = options.hash; -          const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -          const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); -          return new Handlebars.SafeString( -            '<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />' -          ); -        }, -        t(defaultString) { -          return new Handlebars.SafeString(defaultString); -        }, -      }) -      .toCompileTo('<input aria-label="Name" placeholder="Example User" />'); -  }); - -  it('multiple subexpressions in a hash with context', () => { -    expectTemplate('{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}') -      .withInput({ -        item: { -          field: 'Name', -          placeholder: 'Example User', -        }, -      }) -      .withHelpers({ -        input(options) { -          const hash = options.hash; -          const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -          const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); -          return new Handlebars.SafeString( -            '<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />' -          ); -        }, -        t(defaultString) { -          return new Handlebars.SafeString(defaultString); -        }, -      }) -      .toCompileTo('<input aria-label="Name" placeholder="Example User" />'); -  }); - -  it('subexpression functions on the context', () => { -    expectTemplate('{{foo (bar)}}!') -      .withInput({ -        bar() { -          return 'LOL'; -        }, -      }) -      .withHelpers({ -        foo(val) { -          return val + val; -        }, -      }) -      .toCompileTo('LOLLOL!'); -  }); - -  it("subexpressions can't just be property lookups", () => { -    expectTemplate('{{foo (bar)}}!') -      .withInput({ -        bar: 'LOL', -      }) -      .withHelpers({ -        foo(val) { -          return val + val; -        }, -      }) -      .toThrow(); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.utils.test.ts b/dev/lib/handlebars/src/spec/index.utils.test.ts deleted file mode 100644 index 6350bc7c..00000000 --- a/dev/lib/handlebars/src/spec/index.utils.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from '../..'; -import { expectTemplate } from '../__jest__/test_bench'; - -describe('utils', function () { -  describe('#SafeString', function () { -    it('constructing a safestring from a string and checking its type', function () { -      const safe = new Handlebars.SafeString('testing 1, 2, 3'); -      expect(safe).toBeInstanceOf(Handlebars.SafeString); -      expect(safe.toString()).toEqual('testing 1, 2, 3'); -    }); - -    it('it should not escape SafeString properties', function () { -      const name = new Handlebars.SafeString('<em>Sean O'Malley</em>'); -      expectTemplate('{{name}}').withInput({ name }).toCompileTo('<em>Sean O'Malley</em>'); -    }); -  }); -}); diff --git a/dev/lib/handlebars/src/spec/index.whitespace_control.test.ts b/dev/lib/handlebars/src/spec/index.whitespace_control.test.ts deleted file mode 100644 index 1f7cf019..00000000 --- a/dev/lib/handlebars/src/spec/index.whitespace_control.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), - * and may include modifications made by Elasticsearch B.V. - * Elasticsearch B.V. licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import { expectTemplate } from '../__jest__/test_bench'; - -describe('whitespace control', () => { -  it('should strip whitespace around mustache calls', () => { -    const hash = { foo: 'bar<' }; -    expectTemplate(' {{~foo~}} ').withInput(hash).toCompileTo('bar<'); -    expectTemplate(' {{~foo}} ').withInput(hash).toCompileTo('bar< '); -    expectTemplate(' {{foo~}} ').withInput(hash).toCompileTo(' bar<'); -    expectTemplate(' {{~&foo~}} ').withInput(hash).toCompileTo('bar<'); -    expectTemplate(' {{~{foo}~}} ').withInput(hash).toCompileTo('bar<'); -    expectTemplate('1\n{{foo~}} \n\n 23\n{{bar}}4').toCompileTo('1\n23\n4'); -  }); - -  describe('blocks', () => { -    it('should strip whitespace around simple block calls', () => { -      const hash = { foo: 'bar<' }; - -      expectTemplate(' {{~#if foo~}} bar {{~/if~}} ').withInput(hash).toCompileTo('bar'); -      expectTemplate(' {{#if foo~}} bar {{/if~}} ').withInput(hash).toCompileTo(' bar '); -      expectTemplate(' {{~#if foo}} bar {{~/if}} ').withInput(hash).toCompileTo(' bar '); -      expectTemplate(' {{#if foo}} bar {{/if}} ').withInput(hash).toCompileTo('  bar  '); - -      expectTemplate(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ') -        .withInput(hash) -        .toCompileTo('bar'); - -      expectTemplate(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ') -        .withInput(hash) -        .toCompileTo(' abara '); -    }); - -    it('should strip whitespace around inverse block calls', () => { -      expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar'); -      expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar '); -      expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar '); -      expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo('  bar  '); -      expectTemplate(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ').toCompileTo('bar'); -    }); - -    it('should strip whitespace around complex block calls', () => { -      const hash = { foo: 'bar<' }; - -      expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); -      expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo('bar '); -      expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo(' bar'); -      expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo(' bar '); -      expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); - -      expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') -        .withInput(hash) -        .toCompileTo('bar'); - -      expectTemplate('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') -        .withInput(hash) -        .toCompileTo('bar<'); - -      expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo('baz'); -      expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz '); -      expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo(' baz'); -      expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo(' baz '); -      expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo('baz'); -      expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n').toCompileTo( -        'baz' -      ); -    }); -  }); - -  it('should strip whitespace around partials', () => { -    expectTemplate('foo {{~> dude~}} ').withPartials({ dude: 'bar' }).toCompileTo('foobar'); -    expectTemplate('foo {{> dude~}} ').withPartials({ dude: 'bar' }).toCompileTo('foo bar'); -    expectTemplate('foo {{> dude}} ').withPartials({ dude: 'bar' }).toCompileTo('foo bar '); -    expectTemplate('foo\n {{~> dude}} ').withPartials({ dude: 'bar' }).toCompileTo('foobar'); -    expectTemplate('foo\n {{> dude}} ').withPartials({ dude: 'bar' }).toCompileTo('foo\n bar'); -  }); - -  it('should only strip whitespace once', () => { -    expectTemplate(' {{~foo~}} {{foo}} {{foo}} ') -      .withInput({ foo: 'bar' }) -      .toCompileTo('barbar bar '); -  }); -}); diff --git a/dev/lib/handlebars/src/symbols.ts b/dev/lib/handlebars/src/symbols.ts deleted file mode 100644 index 85a8f2f3..00000000 --- a/dev/lib/handlebars/src/symbols.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -export const kHelper = Symbol('helper'); -export const kAmbiguous = Symbol('ambiguous'); -export const kSimple = Symbol('simple'); diff --git a/dev/lib/handlebars/src/types.ts b/dev/lib/handlebars/src/types.ts deleted file mode 100644 index 583170cb..00000000 --- a/dev/lib/handlebars/src/types.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -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 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?: CompileOptions -  ): TemplateDelegateFixed; - -  // -------------------------------------------------------- -  // 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. -  // -------------------------------------------------------- - -  /** -   * 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 - -  /** -   * 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 - -  /** -   * 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 -} - -/** - * Supported Handlebars compile options. - * - * This is a subset of all the compile options supported by the upstream - * Handlebars module. - */ -export type CompileOptions = Pick< -  HandlebarsCompileOptions, -  | 'data' -  | 'knownHelpers' -  | 'knownHelpersOnly' -  | 'noEscape' -  | 'strict' -  | 'assumeObjects' -  | 'preventIndent' -  | 'explicitPartialContext' ->; - -/** - * Supported Handlebars runtime options - * - * This is a subset of all the runtime options supported by the upstream - * Handlebars module. - */ -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; -} - -/** - * 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 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; - -// ----------------------------------------------------------------------------- -// 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 VisitorHelper { -  fn?: HelperDelegateFixed; -  context: any[]; -  params: any[]; -  options: AmbiguousHelperOptions; -} - -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: 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?: HelperDelegateFixed; -    blockHelperMissing?: HelperDelegateFixed; -  }; -} diff --git a/dev/lib/handlebars/src/utils.ts b/dev/lib/handlebars/src/utils.ts deleted file mode 100644 index f55bd98a..00000000 --- a/dev/lib/handlebars/src/utils.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -// @ts-expect-error: Could not find a declaration file for module -import { createFrame } from 'handlebars/dist/cjs/handlebars/utils'; - -import type { AmbiguousHelperOptions, DecoratorOptions } from './types'; - -export function isBlock(node: hbs.AST.Node): node is hbs.AST.BlockStatement { -  return 'program' in node || 'inverse' in node; -} - -export function isDecorator( -  node: hbs.AST.Node -): node is hbs.AST.Decorator | hbs.AST.DecoratorBlock { -  return node.type === 'Decorator' || node.type === 'DecoratorBlock'; -} - -export function toDecoratorOptions(options: AmbiguousHelperOptions) { -  // There's really no tests/documentation on this, but to match the upstream codebase we'll remove `lookupProperty` from the decorator context -  delete (options as any).lookupProperty; - -  return options as DecoratorOptions; -} - -export function noop() { -  return ''; -} - -// liftet from handlebars lib/handlebars/runtime.js -export function initData(context: any, data: any) { -  if (!data || !('root' in data)) { -    data = data ? createFrame(data) : {}; -    data.root = context; -  } -  return data; -} - -// liftet from handlebars lib/handlebars/compiler/compiler.js -export function transformLiteralToPath(node: { path: hbs.AST.PathExpression | hbs.AST.Literal }) { -  const pathIsLiteral = 'parts' in node.path === false; - -  if (pathIsLiteral) { -    const literal = node.path; -    // @ts-expect-error: Not all `hbs.AST.Literal` sub-types has an `original` property, but that's ok, in that case we just want `undefined` -    const original = literal.original; -    // Casting to string here to make false and 0 literal values play nicely with the rest -    // of the system. -    node.path = { -      type: 'PathExpression', -      data: false, -      depth: 0, -      parts: [original + ''], -      original: original + '', -      loc: literal.loc, -    }; -  } -} - -export function allowUnsafeEval() { -  try { -    new Function(); -    return true; -  } catch (e) { -    return false; -  } -} diff --git a/dev/lib/handlebars/src/visitor.ts b/dev/lib/handlebars/src/visitor.ts deleted file mode 100644 index 1842c8e5..00000000 --- a/dev/lib/handlebars/src/visitor.ts +++ /dev/null @@ -1,778 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -import Handlebars from 'handlebars'; -import { -  createProtoAccessControl, -  resultIsAllowed, -  // @ts-expect-error: Could not find a declaration file for module -} from 'handlebars/dist/cjs/handlebars/internal/proto-access'; -// @ts-expect-error: Could not find a declaration file for module -import AST from 'handlebars/dist/cjs/handlebars/compiler/ast'; -// @ts-expect-error: Could not find a declaration file for module -import { indexOf, createFrame } from 'handlebars/dist/cjs/handlebars/utils'; -// @ts-expect-error: Could not find a declaration file for module -import { moveHelperToHooks } from 'handlebars/dist/cjs/handlebars/helpers'; - -import type { -  AmbiguousHelperOptions, -  CompileOptions, -  Container, -  DecoratorDelegate, -  DecoratorsHash, -  HelperOptions, -  NodeType, -  NonBlockHelperOptions, -  ProcessableBlockStatementNode, -  ProcessableNode, -  ProcessableNodeWithPathParts, -  ProcessableNodeWithPathPartsOrLiteral, -  ProcessableStatementNode, -  ResolvePartialOptions, -  RuntimeOptions, -  Template, -  TemplateDelegate, -  VisitorHelper, -} from './types'; -import { kAmbiguous, kHelper, kSimple } from './symbols'; -import { -  initData, -  isBlock, -  isDecorator, -  noop, -  toDecoratorOptions, -  transformLiteralToPath, -} from './utils'; - -export class ElasticHandlebarsVisitor extends Handlebars.Visitor { -  private env: typeof Handlebars; -  private contexts: any[] = []; -  private output: any[] = []; -  private template?: string; -  private compileOptions: CompileOptions; -  private runtimeOptions?: RuntimeOptions; -  private blockParamNames: any[][] = []; -  private blockParamValues: any[][] = []; -  private ast?: hbs.AST.Program; -  private container: Container; -  private defaultHelperOptions: Pick<NonBlockHelperOptions, 'lookupProperty'>; -  private processedRootDecorators = false; // Root decorators should not have access to input arguments. This flag helps us detect them. -  private processedDecoratorsForProgram = new Set(); // It's important that a given program node only has its decorators run once, we use this Map to keep track of them - -  constructor( -    env: typeof Handlebars, -    input: string | hbs.AST.Program, -    options: CompileOptions = {} -  ) { -    super(); - -    this.env = env; - -    if (typeof input !== 'string' && input.type === 'Program') { -      this.ast = input; -    } else { -      this.template = input as string; -    } - -    this.compileOptions = { data: true, ...options }; -    this.compileOptions.knownHelpers = Object.assign( -      Object.create(null), -      { -        helperMissing: true, -        blockHelperMissing: true, -        each: true, -        if: true, -        unless: true, -        with: true, -        log: true, -        lookup: true, -      }, -      this.compileOptions.knownHelpers -    ); - -    const protoAccessControl = createProtoAccessControl({}); - -    const container: Container = (this.container = { -      helpers: {}, -      partials: {}, -      decorators: {}, -      strict(obj, name, loc) { -        if (!obj || !(name in obj)) { -          throw new Handlebars.Exception('"' + name + '" not defined in ' + obj, { -            loc, -          } as hbs.AST.Node); -        } -        return container.lookupProperty(obj, name); -      }, -      // this function is lifted from the handlebars source and slightly modified (lib/handlebars/runtime.js) -      lookupProperty(parent, propertyName) { -        const result = parent[propertyName]; -        if (result == null) { -          return result; -        } -        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { -          return result; -        } - -        if (resultIsAllowed(result, protoAccessControl, propertyName)) { -          return result; -        } -        return undefined; -      }, -      // this function is lifted from the handlebars source and slightly modified (lib/handlebars/runtime.js) -      lambda(current, context) { -        return typeof current === 'function' ? current.call(context) : current; -      }, -      data(value: any, depth: number) { -        while (value && depth--) { -          value = value._parent; -        } -        return value; -      }, -      hooks: {}, -    }); - -    this.defaultHelperOptions = { -      lookupProperty: container.lookupProperty, -    }; -  } - -  render(context: any, options: RuntimeOptions = {}): string { -    this.contexts = [context]; -    this.output = []; -    this.runtimeOptions = { ...options }; -    this.container.helpers = { ...this.env.helpers, ...options.helpers }; -    this.container.partials = { ...this.env.partials, ...options.partials }; -    this.container.decorators = { -      ...(this.env.decorators as DecoratorsHash), -      ...options.decorators, -    }; -    this.container.hooks = {}; -    this.processedRootDecorators = false; -    this.processedDecoratorsForProgram.clear(); - -    if (this.compileOptions.data) { -      this.runtimeOptions.data = initData(context, this.runtimeOptions.data); -    } - -    const keepHelperInHelpers = false; -    moveHelperToHooks(this.container, 'helperMissing', keepHelperInHelpers); -    moveHelperToHooks(this.container, 'blockHelperMissing', keepHelperInHelpers); - -    if (!this.ast) { -      this.ast = Handlebars.parse(this.template!); -    } - -    // The `defaultMain` function contains the default behavior: -    // -    // Generate a "program" function based on the root `Program` in the AST and -    // call it. This will start the processing of all the child nodes in the -    // AST. -    const defaultMain: TemplateDelegate = (_context) => { -      const prog = this.generateProgramFunction(this.ast!); -      return prog(_context, this.runtimeOptions); -    }; - -    // Run any decorators that might exist on the root: -    // -    // The `defaultMain` function is passed in, and if there are no root -    // decorators, or if the decorators chooses to do so, the same function is -    // returned from `processDecorators` and the default behavior is retained. -    // -    // Alternatively any of the root decorators might call the `defaultMain` -    // function themselves, process its return value, and return a completely -    // different `main` function. -    const main = this.processDecorators(this.ast, defaultMain); -    this.processedRootDecorators = true; - -    // Call the `main` function and add the result to the final output. -    const result = main(this.context, options); - -    if (main === defaultMain) { -      this.output.push(result); -      return this.output.join(''); -    } else { -      // We normally expect the return value of `main` to be a string. However, -      // if a decorator is used to override the `defaultMain` function, the -      // return value can be any type. To match the upstream handlebars project -      // behavior, we want the result of rendering the template to be the -      // literal value returned by the decorator. -      // -      // Since the output array in this case always will be empty, we just -      // return that single value instead of attempting to join all the array -      // elements as strings. -      return result; -    } -  } - -  // ********************************************** // -  // ***    Visitor AST Traversal Functions     *** // -  // ********************************************** // - -  Program(program: hbs.AST.Program) { -    this.blockParamNames.unshift(program.blockParams); -    super.Program(program); -    this.blockParamNames.shift(); -  } - -  MustacheStatement(mustache: hbs.AST.MustacheStatement) { -    this.processStatementOrExpression(mustache); -  } - -  BlockStatement(block: hbs.AST.BlockStatement) { -    this.processStatementOrExpression(block); -  } - -  PartialStatement(partial: hbs.AST.PartialStatement) { -    this.invokePartial(partial); -  } - -  PartialBlockStatement(partial: hbs.AST.PartialBlockStatement) { -    this.invokePartial(partial); -  } - -  // This space is intentionally left blank: We want to override the Visitor -  // class implementation of this method, but since we handle decorators -  // separately before traversing the nodes, we just want to make this a no-op. -  DecoratorBlock(decorator: hbs.AST.DecoratorBlock) {} - -  // This space is intentionally left blank: We want to override the Visitor -  // class implementation of this method, but since we handle decorators -  // separately before traversing the nodes, we just want to make this a no-op. -  Decorator(decorator: hbs.AST.Decorator) {} - -  SubExpression(sexpr: hbs.AST.SubExpression) { -    this.processStatementOrExpression(sexpr); -  } - -  PathExpression(path: hbs.AST.PathExpression) { -    const blockParamId = -      !path.depth && !AST.helpers.scopedId(path) && this.blockParamIndex(path.parts[0]); - -    let result; -    if (blockParamId) { -      result = this.lookupBlockParam(blockParamId, path); -    } else if (path.data) { -      result = this.lookupData(this.runtimeOptions!.data, path); -    } else { -      result = this.resolvePath(this.contexts[path.depth], path); -    } - -    this.output.push(result); -  } - -  ContentStatement(content: hbs.AST.ContentStatement) { -    this.output.push(content.value); -  } - -  StringLiteral(string: hbs.AST.StringLiteral) { -    this.output.push(string.value); -  } - -  NumberLiteral(number: hbs.AST.NumberLiteral) { -    this.output.push(number.value); -  } - -  BooleanLiteral(bool: hbs.AST.BooleanLiteral) { -    this.output.push(bool.value); -  } - -  UndefinedLiteral() { -    this.output.push(undefined); -  } - -  NullLiteral() { -    this.output.push(null); -  } - -  // ********************************************** // -  // ***      Visitor AST Helper Functions      *** // -  // ********************************************** // - -  /** -   * Special code for decorators, since they have to be executed ahead of time (before the wrapping program). -   * So we have to look into the program AST body and see if it contains any decorators that we have to process -   * before we can finish processing of the wrapping program. -   */ -  private processDecorators(program: hbs.AST.Program, prog: TemplateDelegate) { -    if (!this.processedDecoratorsForProgram.has(program)) { -      this.processedDecoratorsForProgram.add(program); -      const props = {}; -      for (const node of program.body) { -        if (isDecorator(node)) { -          prog = this.processDecorator(node, prog, props); -        } -      } -    } - -    return prog; -  } - -  private processDecorator( -    decorator: hbs.AST.DecoratorBlock | hbs.AST.Decorator, -    prog: TemplateDelegate, -    props: Record<string, any> -  ) { -    const options = this.setupDecoratorOptions(decorator); - -    const result = this.container.lookupProperty<DecoratorDelegate>( -      this.container.decorators, -      options.name -    )(prog, props, this.container, options); - -    return Object.assign(result || prog, props); -  } - -  private processStatementOrExpression(node: ProcessableNodeWithPathPartsOrLiteral) { -    // Calling `transformLiteralToPath` has side-effects! -    // It converts a node from type `ProcessableNodeWithPathPartsOrLiteral` to `ProcessableNodeWithPathParts` -    transformLiteralToPath(node); - -    switch (this.classifyNode(node as ProcessableNodeWithPathParts)) { -      case kSimple: -        this.processSimpleNode(node as ProcessableNodeWithPathParts); -        break; -      case kHelper: -        this.processHelperNode(node as ProcessableNodeWithPathParts); -        break; -      case kAmbiguous: -        this.processAmbiguousNode(node as ProcessableNodeWithPathParts); -        break; -    } -  } - -  // Liftet from lib/handlebars/compiler/compiler.js (original name: classifySexpr) -  private classifyNode(node: { path: hbs.AST.PathExpression }): NodeType { -    const isSimple = AST.helpers.simpleId(node.path); -    const isBlockParam = isSimple && !!this.blockParamIndex(node.path.parts[0]); - -    // a mustache is an eligible helper if: -    // * its id is simple (a single part, not `this` or `..`) -    let isHelper = !isBlockParam && AST.helpers.helperExpression(node); - -    // if a mustache is an eligible helper but not a definite -    // helper, it is ambiguous, and will be resolved in a later -    // pass or at runtime. -    let isEligible = !isBlockParam && (isHelper || isSimple); - -    // if ambiguous, we can possibly resolve the ambiguity now -    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. -    if (isEligible && !isHelper) { -      const name = node.path.parts[0]; -      const options = this.compileOptions; -      if (options.knownHelpers && options.knownHelpers[name]) { -        isHelper = true; -      } else if (options.knownHelpersOnly) { -        isEligible = false; -      } -    } - -    if (isHelper) { -      return kHelper; -    } else if (isEligible) { -      return kAmbiguous; -    } else { -      return kSimple; -    } -  } - -  // Liftet from lib/handlebars/compiler/compiler.js -  private blockParamIndex(name: string): [number, any] | undefined { -    for (let depth = 0, len = this.blockParamNames.length; depth < len; depth++) { -      const blockParams = this.blockParamNames[depth]; -      const param = blockParams && indexOf(blockParams, name); -      if (blockParams && param >= 0) { -        return [depth, param]; -      } -    } -  } - -  // Looks up the value of `parts` on the given block param and pushes -  // it onto the stack. -  private lookupBlockParam(blockParamId: [number, any], path: hbs.AST.PathExpression) { -    const value = this.blockParamValues[blockParamId[0]][blockParamId[1]]; -    return this.resolvePath(value, path, 1); -  } - -  // Push the data lookup operator -  private lookupData(data: any, path: hbs.AST.PathExpression) { -    if (path.depth) { -      data = this.container.data(data, path.depth); -    } - -    return this.resolvePath(data, path); -  } - -  private processSimpleNode(node: ProcessableNodeWithPathParts) { -    const path = node.path; -    // @ts-expect-error strict is not a valid property on PathExpression, but we used in the same way it's also used in the original handlebars -    path.strict = true; -    const result = this.resolveNodes(path)[0]; -    const lambdaResult = this.container.lambda(result, this.context); - -    if (isBlock(node)) { -      this.blockValue(node, lambdaResult); -    } else { -      this.output.push(lambdaResult); -    } -  } - -  // The purpose of this opcode is to take a block of the form -  // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and -  // replace it on the stack with the result of properly -  // invoking blockHelperMissing. -  private blockValue(node: hbs.AST.BlockStatement, value: any) { -    const name = node.path.original; -    const options = this.setupParams(node, name); - -    const result = this.container.hooks.blockHelperMissing!.call(this.context, value, options); - -    this.output.push(result); -  } - -  private processHelperNode(node: ProcessableNodeWithPathParts) { -    const path = node.path; -    const name = path.parts[0]; - -    if (this.compileOptions.knownHelpers && this.compileOptions.knownHelpers[name]) { -      this.invokeKnownHelper(node); -    } else if (this.compileOptions.knownHelpersOnly) { -      throw new Handlebars.Exception( -        'You specified knownHelpersOnly, but used the unknown helper ' + name, -        node -      ); -    } else { -      this.invokeHelper(node); -    } -  } - -  // This operation is used when the helper is known to exist, -  // so a `helperMissing` fallback is not required. -  private invokeKnownHelper(node: ProcessableNodeWithPathParts) { -    const name = node.path.parts[0]; -    const helper = this.setupHelper(node, name); -    // TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards -    const result = helper.fn!.call(helper.context, ...helper.params, helper.options); -    this.output.push(result); -  } - -  // Pops off the helper's parameters, invokes the helper, -  // and pushes the helper's return value onto the stack. -  // -  // If the helper is not found, `helperMissing` is called. -  private invokeHelper(node: ProcessableNodeWithPathParts) { -    const path = node.path; -    const name = path.original; -    const isSimple = AST.helpers.simpleId(path); -    const helper = this.setupHelper(node, name); - -    const loc = isSimple && helper.fn ? node.loc : path.loc; -    helper.fn = (isSimple && helper.fn) || this.resolveNodes(path)[0]; - -    if (!helper.fn) { -      if (this.compileOptions.strict) { -        helper.fn = this.container.strict(helper.context, name, loc); -      } else { -        helper.fn = this.container.hooks.helperMissing; -      } -    } - -    // TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards -    const result = helper.fn!.call(helper.context, ...helper.params, helper.options); - -    this.output.push(result); -  } - -  private invokePartial(partial: hbs.AST.PartialStatement | hbs.AST.PartialBlockStatement) { -    const { params } = partial; -    if (params.length > 1) { -      throw new Handlebars.Exception( -        `Unsupported number of partial arguments: ${params.length}`, -        partial -      ); -    } - -    const isDynamic = partial.name.type === 'SubExpression'; -    const name = isDynamic -      ? this.resolveNodes(partial.name).join('') -      : (partial.name as hbs.AST.PathExpression).original; - -    const options: AmbiguousHelperOptions & ResolvePartialOptions = this.setupParams(partial, name); -    options.helpers = this.container.helpers; -    options.partials = this.container.partials; -    options.decorators = this.container.decorators; - -    let partialBlock; -    if ('fn' in options && options.fn !== noop) { -      const { fn } = options; -      const currentPartialBlock = options.data?.['partial-block']; -      options.data = createFrame(options.data); - -      // Wrapper function to get access to currentPartialBlock from the closure -      partialBlock = options.data['partial-block'] = function partialBlockWrapper( -        context: any, -        wrapperOptions: { data?: HelperOptions['data'] } = {} -      ) { -        // Restore the partial-block from the closure for the execution of the block -        // i.e. the part inside the block of the partial call. -        wrapperOptions.data = createFrame(wrapperOptions.data); -        wrapperOptions.data['partial-block'] = currentPartialBlock; -        return fn(context, wrapperOptions); -      }; - -      if (fn.partials) { -        options.partials = { ...options.partials, ...fn.partials }; -      } -    } - -    let context = {}; -    if (params.length === 0 && !this.compileOptions.explicitPartialContext) { -      context = this.context; -    } else if (params.length === 1) { -      context = this.resolveNodes(params[0])[0]; -    } - -    if (Object.keys(options.hash).length > 0) { -      // TODO: context can be an array, but maybe never when we have a hash??? -      context = Object.assign({}, context, options.hash); -    } - -    const partialTemplate: Template | undefined = -      this.container.partials[name] ?? -      partialBlock ?? -      // TypeScript note: We extend ResolvePartialOptions in our types.ts file -      // to fix an error in the upstream type. When calling back into the -      // upstream code, we just cast back to the non-extended type -      Handlebars.VM.resolvePartial( -        undefined, -        undefined, -        options as Handlebars.ResolvePartialOptions -      ); - -    if (partialTemplate === undefined) { -      throw new Handlebars.Exception(`The partial ${name} could not be found`); -    } - -    let render; -    if (typeof partialTemplate === 'string') { -      render = this.env.compileAST(partialTemplate, this.compileOptions); -      if (name in this.container.partials) { -        this.container.partials[name] = render; -      } -    } else { -      render = partialTemplate; -    } - -    let result = render(context, options); - -    if ('indent' in partial) { -      result = -        partial.indent + -        (this.compileOptions.preventIndent -          ? result -          : result.replace(/\n(?!$)/g, `\n${partial.indent}`)); // indent each line, ignoring any trailing linebreak -    } - -    this.output.push(result); -  } - -  private processAmbiguousNode(node: ProcessableNodeWithPathParts) { -    const name = node.path.parts[0]; -    const helper = this.setupHelper(node, name); -    let { fn: helperFn } = helper; - -    const loc = helperFn ? node.loc : node.path.loc; -    helperFn = helperFn ?? this.resolveNodes(node.path)[0]; - -    if (helperFn === undefined) { -      if (this.compileOptions.strict) { -        helperFn = this.container.strict(helper.context, name, loc); -      } else { -        helperFn = -          helper.context != null -            ? this.container.lookupProperty(helper.context, name) -            : helper.context; -        if (helperFn == null) helperFn = this.container.hooks.helperMissing; -      } -    } - -    const helperResult = -      typeof helperFn === 'function' -        ? helperFn.call(helper.context, ...helper.params, helper.options) -        : helperFn; - -    if (isBlock(node)) { -      const result = helper.fn -        ? helperResult -        : this.container.hooks.blockHelperMissing!.call(this.context, helperResult, helper.options); -      if (result != null) { -        this.output.push(result); -      } -    } else { -      if ( -        (node as hbs.AST.MustacheStatement).escaped === false || -        this.compileOptions.noEscape === true || -        typeof helperResult !== 'string' -      ) { -        this.output.push(helperResult); -      } else { -        this.output.push(Handlebars.escapeExpression(helperResult)); -      } -    } -  } - -  private setupHelper(node: ProcessableNode, helperName: string): VisitorHelper { -    return { -      fn: this.container.lookupProperty(this.container.helpers, helperName), -      context: this.context, -      params: this.resolveNodes(node.params), -      options: this.setupParams(node, helperName), -    }; -  } - -  private setupDecoratorOptions(decorator: hbs.AST.Decorator | hbs.AST.DecoratorBlock) { -    // TypeScript: The types indicate that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too. -    const name = (decorator.path as hbs.AST.PathExpression).original; -    const options = toDecoratorOptions(this.setupParams(decorator, name)); - -    if (decorator.params.length > 0) { -      if (!this.processedRootDecorators) { -        // When processing the root decorators, temporarily remove the root context so it's not accessible to the decorator -        const context = this.contexts.shift(); -        options.args = this.resolveNodes(decorator.params); -        this.contexts.unshift(context); -      } else { -        options.args = this.resolveNodes(decorator.params); -      } -    } else { -      options.args = []; -    } - -    return options; -  } - -  private setupParams(node: ProcessableBlockStatementNode, name: string): HelperOptions; -  private setupParams(node: ProcessableStatementNode, name: string): NonBlockHelperOptions; -  private setupParams(node: ProcessableNode, name: string): AmbiguousHelperOptions; -  private setupParams(node: ProcessableNode, name: string) { -    const options: AmbiguousHelperOptions = { -      name, -      hash: this.getHash(node), -      data: this.runtimeOptions!.data, -      loc: { start: node.loc.start, end: node.loc.end }, -      ...this.defaultHelperOptions, -    }; - -    if (isBlock(node)) { -      (options as HelperOptions).fn = node.program -        ? this.processDecorators(node.program, this.generateProgramFunction(node.program)) -        : noop; -      (options as HelperOptions).inverse = node.inverse -        ? this.processDecorators(node.inverse, this.generateProgramFunction(node.inverse)) -        : noop; -    } - -    return options; -  } - -  private generateProgramFunction(program: hbs.AST.Program) { -    if (!program) return noop; - -    const prog: TemplateDelegate = (nextContext: any, runtimeOptions: RuntimeOptions = {}) => { -      runtimeOptions = { ...runtimeOptions }; - -      // inherit data in blockParams from parent program -      runtimeOptions.data = runtimeOptions.data || this.runtimeOptions!.data; -      if (runtimeOptions.blockParams) { -        runtimeOptions.blockParams = runtimeOptions.blockParams.concat( -          this.runtimeOptions!.blockParams -        ); -      } - -      // inherit partials from parent program -      runtimeOptions.partials = runtimeOptions.partials || this.runtimeOptions!.partials; - -      // stash parent program data -      const tmpRuntimeOptions = this.runtimeOptions; -      this.runtimeOptions = runtimeOptions; -      const shiftContext = nextContext !== this.context; -      if (shiftContext) this.contexts.unshift(nextContext); -      this.blockParamValues.unshift(runtimeOptions.blockParams || []); - -      // execute child program -      const result = this.resolveNodes(program).join(''); - -      // unstash parent program data -      this.blockParamValues.shift(); -      if (shiftContext) this.contexts.shift(); -      this.runtimeOptions = tmpRuntimeOptions; - -      // return result of child program -      return result; -    }; - -    prog.blockParams = program.blockParams?.length ?? 0; -    return prog; -  } - -  private getHash(statement: { hash?: hbs.AST.Hash }) { -    const result: { [key: string]: any } = {}; -    if (!statement.hash) return result; -    for (const { key, value } of statement.hash.pairs) { -      result[key] = this.resolveNodes(value)[0]; -    } -    return result; -  } - -  private resolvePath(obj: any, path: hbs.AST.PathExpression, index = 0) { -    if (this.compileOptions.strict || this.compileOptions.assumeObjects) { -      return this.strictLookup(obj, path); -    } - -    for (; index < path.parts.length; index++) { -      if (obj == null) return; -      obj = this.container.lookupProperty(obj, path.parts[index]); -    } - -    return obj; -  } - -  private strictLookup(obj: any, path: hbs.AST.PathExpression) { -    // @ts-expect-error strict is not a valid property on PathExpression, but we used in the same way it's also used in the original handlebars -    const requireTerminal = this.compileOptions.strict && path.strict; -    const len = path.parts.length - (requireTerminal ? 1 : 0); - -    for (let i = 0; i < len; i++) { -      obj = this.container.lookupProperty(obj, path.parts[i]); -    } - -    if (requireTerminal) { -      return this.container.strict(obj, path.parts[len], path.loc); -    } else { -      return obj; -    } -  } - -  private resolveNodes(nodes: hbs.AST.Node | hbs.AST.Node[]): any[] { -    const currentOutput = this.output; -    this.output = []; - -    if (Array.isArray(nodes)) { -      this.acceptArray(nodes); -    } else { -      this.accept(nodes); -    } - -    const result = this.output; - -    this.output = currentOutput; - -    return result; -  } - -  private get context() { -    return this.contexts[0]; -  } -} diff --git a/dev/lib/handlebars/tsconfig.json b/dev/lib/handlebars/tsconfig.json deleted file mode 100644 index 3f139716..00000000 --- a/dev/lib/handlebars/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ -  "compilerOptions": { -    "outDir": "target/types", -    "types": [ -      "jest", -      "node" -    ] -  }, -  "include": [ -    "**/*.ts" -  ], -  "exclude": [ -    "target/**/*", -  ] -} |