Compare commits
244 Commits
v4.7.4
...
precompile-esm
| Author | SHA1 | Date | |
|---|---|---|---|
| 88f789e419 | |||
| 8ce2be4496 | |||
| d3b93571a7 | |||
| 99d162cadc | |||
| 7147755b55 | |||
| 9413ea99ce | |||
| f202c501a3 | |||
| bb55dead2d | |||
| 6e10b1f658 | |||
| 65f24eb8b6 | |||
| 35f001862c | |||
| b3ffb16e5d | |||
| 42adee3f82 | |||
| b4a54ad86e | |||
| e6686963ba | |||
| 262b84b08a | |||
| 6186128ad0 | |||
| 03fe783a69 | |||
| e534a911f3 | |||
| f6ff3bf52b | |||
| da41887377 | |||
| 83baaa469f | |||
| 785a63e0a8 | |||
| 903ca504b5 | |||
| 69dbe65c19 | |||
| 54cbc8c6c6 | |||
| b14ff4a6fc | |||
| e0de1d9a4a | |||
| 9bff03fd3b | |||
| a9fe6d5a03 | |||
| ce8982fe55 | |||
| 80809ef7fc | |||
| 30bd78d457 | |||
| 0896d00c2e | |||
| f73be4ebf3 | |||
| 29ed243268 | |||
| dd6ff05079 | |||
| c18ed16f71 | |||
| 7e5890cf5a | |||
| c7278d176a | |||
| 35ea8e61bf | |||
| a98b01c2ee | |||
| b3b26712d9 | |||
| 37f4ea18b4 | |||
| 48870b41f7 | |||
| 82c613244a | |||
| e2f63da5c0 | |||
| 4da5882660 | |||
| 78e7e28ff9 | |||
| 03d387bf8e | |||
| e0f50b4eec | |||
| 9ed9418488 | |||
| ef0fc290b9 | |||
| edc65b5c19 | |||
| 715f4af179 | |||
| 3bd0fa8b32 | |||
| 476d61667d | |||
| 5ba0c72fe6 | |||
| 49621c15ac | |||
| 1dc2c0bffb | |||
| fe58756da2 | |||
| c295ef085f | |||
| c1ad3c8057 | |||
| af92e32822 | |||
| 2954e7ea66 | |||
| b0f1a62ddb | |||
| 7d9450d3c3 | |||
| 8eefee56ff | |||
| e538c29d92 | |||
| fd93073146 | |||
| 6790c080c6 | |||
| a9a8e40321 | |||
| e66aed5b99 | |||
| 7d4d170ce4 | |||
| eb860c0899 | |||
| 2f509a8fca | |||
| 19bdace85a | |||
| b6d3de7123 | |||
| f058970169 | |||
| 071003b89c | |||
| 1a08e1d0a7 | |||
| 212f9d930b | |||
| 77825f8d35 | |||
| a65a55382c | |||
| b1034a6702 | |||
| 8727d2015a | |||
| a39bfa5fe3 | |||
| 3789a30955 | |||
| 75794c5dbc | |||
| 3438891bd2 | |||
| 0ec71dcc5c | |||
| e6ad93ea01 | |||
| 2bf4fc6fd3 | |||
| b64202bc91 | |||
| c2f1e62031 | |||
| 08e9a11a34 | |||
| 6f17a8e75c | |||
| 1fd2edee2a | |||
| 3c9c2f5cf2 | |||
| 16487a088e | |||
| 309d2b49a1 | |||
| 645ac73844 | |||
| b454b02147 | |||
| f8eade7b41 | |||
| d36adf9343 | |||
| 2fd4a3da7a | |||
| 3800b7dd4c | |||
| 2ea32e8db9 | |||
| fd3ca85d13 | |||
| edfe6b899c | |||
| 0c8230c253 | |||
| d23ccf636a | |||
| 8a062f19d5 | |||
| 44ee971e44 | |||
| 37411901da | |||
| dfe0fcb130 | |||
| 3fb331ef40 | |||
| 2f0991a022 | |||
| 4085e066fc | |||
| dce6f54fdf | |||
| 7b11f55c53 | |||
| b0bb2a42b4 | |||
| e46c239b3d | |||
| 081e1d0ad9 | |||
| 2ef6fbd1fc | |||
| 50e844b563 | |||
| c56995d706 | |||
| 566536b03d | |||
| 6aaac4a779 | |||
| 4ae86f3282 | |||
| 25bae31578 | |||
| 74795a3a7f | |||
| 0419daec52 | |||
| 8df74a0119 | |||
| 35d3fdb1dd | |||
| dce44541db | |||
| a510d82252 | |||
| 77e38330e6 | |||
| 3ce4425056 | |||
| 2f8aa1c839 | |||
| 2f681d256f | |||
| 1b04524a53 | |||
| 3fc7e6feb2 | |||
| 16c654579a | |||
| f349aa43c0 | |||
| 913b845aa9 | |||
| 920bf1c5ad | |||
| d1eca1c5cf | |||
| d7d14ed6e5 | |||
| 379172e236 | |||
| 148b19182d | |||
| e1fa310101 | |||
| 94419deb95 | |||
| 63be1c8130 | |||
| 7f3da649c0 | |||
| f21f900cf5 | |||
| 999220bf2d | |||
| d5caa56d6c | |||
| ce3cd8a685 | |||
| 33773c2cb2 | |||
| 7b74175e0e | |||
| c933387415 | |||
| 1a78717286 | |||
| 64002dc506 | |||
| fb26f69485 | |||
| be107d2ca1 | |||
| 2441ba1335 | |||
| 5c676a32fb | |||
| 228950169d | |||
| 680ec9666a | |||
| 58ec656fbc | |||
| 77674e4f6c | |||
| 714a4c4482 | |||
| d40cbfc70d | |||
| b6e2f76a45 | |||
| 1b78b5b7b2 | |||
| a5e6afb05d | |||
| 8baba76b64 | |||
| 508347ea94 | |||
| ad341bfc1b | |||
| 911e676af4 | |||
| 0fa10b6e87 | |||
| 660a117536 | |||
| c9970200a0 | |||
| 6300e57f11 | |||
| 6c89939af7 | |||
| a6476739f0 | |||
| 9119265204 | |||
| d117b3e0f9 | |||
| 2b7ed7e0bb | |||
| 9a36966b16 | |||
| 6c92579faf | |||
| 7535e48a79 | |||
| 11b99b361d | |||
| 275ab37b2e | |||
| bff3af52f2 | |||
| 9fb7baddcc | |||
| 21696005cd | |||
| 601ce8a69f | |||
| bbe0a94d6e | |||
| f2211ddd65 | |||
| e8722e2551 | |||
| 094a2564fe | |||
| a6a0e50551 | |||
| 5c9d5b5b54 | |||
| f0d4159a5e | |||
| 2ff795cf2f | |||
| e8ceafa652 | |||
| 6b644802d9 | |||
| 768ddbd661 | |||
| 06baeaebfd | |||
| c7be766902 | |||
| b37232fc8d | |||
| 0757843712 | |||
| 27212f5953 | |||
| 7130a11539 | |||
| 4788b03192 | |||
| 0b8fd1d8a8 | |||
| 89c8b3a886 | |||
| b55a120e82 | |||
| dfc75545fa | |||
| 400bada31f | |||
| 834695e241 | |||
| 5302782c08 | |||
| 118836f390 | |||
| ee6fadffb4 | |||
| ec64cf9293 | |||
| 9a94d1771f | |||
| 164f1c13f7 | |||
| 8517352e20 | |||
| 63fdb92472 | |||
| 8a9c79b799 | |||
| eb1ec52b45 | |||
| 21bf95c92a | |||
| cc0b239aaf | |||
| a6121cae79 | |||
| 871c32a6f9 | |||
| 326734b0f3 | |||
| 2ea6119a83 | |||
| 8289c0bf3a | |||
| 25458fdc2a | |||
| 63a08890cc | |||
| 0f054efcdb | |||
| 3f77b82ed0 |
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
+1
-3
@@ -4,7 +4,6 @@
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
.idea
|
||||
yarn-error.log
|
||||
node_modules
|
||||
@@ -12,10 +11,9 @@ node_modules
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
lib/handlebars/compiler/parser.js
|
||||
/coverage/
|
||||
/dist/
|
||||
/integration-testing/*/dist/
|
||||
/tests/integration/*/dist/
|
||||
|
||||
# Third-party or files that must remain unchanged
|
||||
/spec/expected/
|
||||
|
||||
+6
-8
@@ -1,11 +1,14 @@
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
|
||||
globals: {
|
||||
self: false
|
||||
self: false,
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
es6: true
|
||||
es2020: true,
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'warn',
|
||||
@@ -56,11 +59,6 @@ module.exports = {
|
||||
|
||||
// ECMAScript 6 //
|
||||
//--------------//
|
||||
'no-var': 'error'
|
||||
'no-var': 'error',
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 6,
|
||||
ecmaFeatures: {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Upgrade to Prettier 2.7
|
||||
3d228334530860a6e3f99dc10777c84bf22292c1
|
||||
# Format markdown files with Prettier
|
||||
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
|
||||
@@ -0,0 +1,9 @@
|
||||
Before filing issues, please check the following points first:
|
||||
|
||||
- [ ] Please don't open issues for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars
|
||||
- [ ] Have a look at https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md
|
||||
- [ ] Read the FAQ at https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md
|
||||
- [ ] Use the jsfiddle-template at https://jsfiddle.net/4nbwjaqz/4/ to reproduce problems or bugs
|
||||
|
||||
This will probably help you to get a solution faster.
|
||||
For bugs, it would be great to have a PR with a failing test-case.
|
||||
@@ -0,0 +1,12 @@
|
||||
Before creating a pull-request, please check https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md first.
|
||||
|
||||
Generally we like to see pull requests that
|
||||
|
||||
- [ ] Please don't start pull requests for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars
|
||||
- [ ] Maintain the existing code style
|
||||
- [ ] Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- [ ] Have good commit messages
|
||||
- [ ] Have tests
|
||||
- [ ] Have the [typings](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) (lib/handlebars.d.ts) updated on every API change. If you need help, updating those, please mention that in the PR description.
|
||||
- [ ] Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
|
||||
- [ ] Currently, the `4.x`-branch contains the latest version. Please target that branch in the PR.
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 0
|
||||
schedule:
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-type: production
|
||||
@@ -0,0 +1,112 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
dependencies:
|
||||
name: Test (dependencies)
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
# Node 14 ships with npm v6, which doesn't install peer-dependencies by default.
|
||||
# Starting with npm v7 (which is shipped with Node >= 16), peer-dependencies are
|
||||
# automatically installed. So this test (check for unmet peer-dependencies) only
|
||||
# works with Node <= 14.
|
||||
node-version: '14'
|
||||
|
||||
# Simulate an installation by a dependent package
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rm package-lock.json
|
||||
npm install --production
|
||||
|
||||
- name: Check dependency tree
|
||||
run: npm ls
|
||||
|
||||
test:
|
||||
name: Test (Node)
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||
# https://nodejs.org/en/about/releases/
|
||||
node-version: ['12', '14', '16', '18', '20']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Test (Integration)
|
||||
run: |
|
||||
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||
cd ./tests/integration/webpack-test && ./test.sh && cd -
|
||||
|
||||
browser:
|
||||
name: Test (Browser)
|
||||
runs-on: 'ubuntu-22.04'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
npx playwright install-deps
|
||||
npx playwright install
|
||||
|
||||
- name: Build
|
||||
run: npx grunt prepare
|
||||
|
||||
- name: Test
|
||||
run: npm run test:browser
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
publish-aws-s3:
|
||||
name: Publish to AWS S3
|
||||
runs-on: 'ubuntu-latest'
|
||||
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
git config --global user.email "release@handlebarsjs.com"
|
||||
git config --global user.name "handlebars-lang"
|
||||
npm run publish:aws
|
||||
env:
|
||||
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
+1
-3
@@ -4,7 +4,6 @@
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
.idea
|
||||
/yarn-error.log
|
||||
/yarn.lock
|
||||
@@ -13,8 +12,7 @@ node_modules
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
lib/handlebars/compiler/parser.js
|
||||
/coverage/
|
||||
/dist/
|
||||
/integration-testing/*/dist/
|
||||
/tests/integration/*/dist/
|
||||
/spec/tmp/*
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = git://github.com/mustache/spec.git
|
||||
url = https://github.com/mustache/spec.git
|
||||
|
||||
+1
-2
@@ -12,10 +12,9 @@ node_modules
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
lib/handlebars/compiler/parser.js
|
||||
/coverage/
|
||||
/dist/
|
||||
/integration-testing/*/dist/
|
||||
/tests/integration/*/dist/
|
||||
|
||||
# Third-party or files that must remain unchanged
|
||||
/spec/expected/
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
language: node_js
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
name: check javascript (eslint)
|
||||
node_js: lts/*
|
||||
script: npm run lint
|
||||
- stage: test
|
||||
name: check formatting (prettier)
|
||||
node_js: lts/*
|
||||
script: npm run check-format
|
||||
- stage: test
|
||||
name: check typescript definitions (dtslint)
|
||||
node_js: lts/*
|
||||
script: npm run dtslint
|
||||
- stage: test
|
||||
name: extensive tests and publish to aws
|
||||
script: npm run extensive-tests-and-publish-to-aws
|
||||
env:
|
||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
||||
- SAUCE_USERNAME=handlebars
|
||||
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
|
||||
node_js: '10'
|
||||
- stage: test
|
||||
name: test with latest nodejs-lts
|
||||
node_js: lts/*
|
||||
script: npm run test
|
||||
- stage: test
|
||||
name: test with active nodejs
|
||||
node_js: node
|
||||
script: npm run test
|
||||
cache: npm
|
||||
email:
|
||||
on_failure: change
|
||||
on_success: never
|
||||
git:
|
||||
depth: 100
|
||||
+36
-31
@@ -1,20 +1,28 @@
|
||||
# How to Contribute
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
Please refer to our [Security Policy](https://github.com/handlebars-lang/handlebars.js/blob/master/SECURITY.md).
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
Please refer to our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]!
|
||||
|
||||
In general, we are going to ask for an **example** of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle **[template][jsfiddle]** to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here).
|
||||
|
||||
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||
|
||||
## Branches
|
||||
|
||||
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||
should merge the branch `4.x` into the master branch regularly.
|
||||
- The branch `3.x` contains the legacy version `3.x`. Bugfixes are applied separately (if needed). The branch will not
|
||||
be merged with any of the other branches.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
@@ -47,9 +55,9 @@ You can also run our set of benchmarks with `grunt bench`.
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||
|
||||
##Running Tests
|
||||
## Running Tests
|
||||
|
||||
To run tests locally, first install all dependencies.
|
||||
|
||||
@@ -78,44 +86,35 @@ We do linting and formatting in two phases:
|
||||
|
||||
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||
while warnings are allowed.
|
||||
- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||
are forbidden.
|
||||
|
||||
You can use the following scripts to make sure that the travis-job does not fail:
|
||||
You can use the following scripts to make sure that the CI job does not fail:
|
||||
|
||||
- **npm run lint** will run `eslint` and fail on warnings
|
||||
- **npm run format** will run `prettier` on all files
|
||||
- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
|
||||
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||
|
||||
## Ember testing
|
||||
|
||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
||||
|
||||
```sh
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm test
|
||||
```
|
||||
|
||||
## Releasing the latest version
|
||||
|
||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
- Push-access to `handlebars-lang/handlebars.js`
|
||||
- Publishing rights on npmjs.com for the `handlebars` package
|
||||
- Publishing rights on gemfury for the `handlebars-source` package
|
||||
- Push-access to the repo for legacy package managers: `components/handlebars`
|
||||
- Push-access to the production-repo of the handlebars site: `handlebars-lang/handlebarsjs.com-github-pages`
|
||||
|
||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
npm ci
|
||||
yo release
|
||||
npx grunt
|
||||
npm publish
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
@@ -130,9 +129,15 @@ in those places still point to the latest version
|
||||
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
|
||||
- [RubyGems](https://rubygems.org/gems/handlebars-source)
|
||||
|
||||
When everything is OK, the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
|
||||
When everything is OK, the **handlebars site** needs to be updated.
|
||||
|
||||
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
|
||||
and make a minimal change to the README. This will invoke a github-action that redeploys
|
||||
the site, fetching the latest version-number from the npm-registry.
|
||||
(note that the default-branch of this repo is not the master and regular changes are done
|
||||
in the `handlebars-lang/docs`-repo).
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||
[jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/
|
||||
|
||||
@@ -1,60 +1,54 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
1. How can I file a bug report:
|
||||
## How can I file a bug report:
|
||||
|
||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why isn't my Mustache template working?
|
||||
## Why isn't my Mustache template working?
|
||||
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
|
||||
1. Why is it slower when compiling?
|
||||
## Why is it slower when compiling?
|
||||
|
||||
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
|
||||
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
|
||||
|
||||
1. Why doesn't this work with Content Security Policy restrictions?
|
||||
## Why doesn't this work with Content Security Policy restrictions?
|
||||
|
||||
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
|
||||
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
|
||||
|
||||
1. How can I include script tags in my template?
|
||||
## How can I include script tags in my template?
|
||||
|
||||
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
|
||||
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
|
||||
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
|
||||
1. Why are my precompiled scripts throwing exceptions?
|
||||
## Why are my precompiled scripts throwing exceptions?
|
||||
|
||||
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
|
||||
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
|
||||
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
If using the integrated precompiler and
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
On the client side.
|
||||
If using the integrated precompiler and
|
||||
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
On the client side.
|
||||
|
||||
1. Why doesn't IE like the `default` name in the AMD module?
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
|
||||
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. How do I load the runtime library when using AMD?
|
||||
## How do I load the runtime library when using AMD?
|
||||
|
||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||
|
||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||
The `handlebars.runtime.js` file includes a UMD build, which exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
+62
-184
@@ -1,32 +1,24 @@
|
||||
/* eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
module.exports = function (grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
clean: [
|
||||
'tmp',
|
||||
'dist',
|
||||
'lib/handlebars/compiler/parser.js',
|
||||
'integration-testing/**/node_modules'
|
||||
],
|
||||
clean: ['tmp', 'dist', 'tests/integration/**/node_modules'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content) {
|
||||
processContent: function (content) {
|
||||
return (
|
||||
grunt.template.process(
|
||||
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||
) + content
|
||||
'/**!\n\n @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||
) +
|
||||
content +
|
||||
'\n// @license-end\n'
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
|
||||
},
|
||||
cdnjs: {
|
||||
files: [
|
||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
|
||||
]
|
||||
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }],
|
||||
},
|
||||
components: {
|
||||
files: [
|
||||
@@ -34,105 +26,63 @@ module.exports = function(grunt) {
|
||||
expand: true,
|
||||
cwd: 'components/',
|
||||
src: ['**'],
|
||||
dest: 'dist/components'
|
||||
dest: 'dist/components',
|
||||
},
|
||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
|
||||
]
|
||||
}
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'dist/',
|
||||
src: ['*.js'],
|
||||
dest: 'dist/components',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMaps: 'inline',
|
||||
loose: ['es6.modules'],
|
||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||
auxiliaryCommentBefore: 'istanbul ignore next',
|
||||
},
|
||||
amd: {
|
||||
options: {
|
||||
modules: 'amd'
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/amd/'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
cjs: {
|
||||
options: {
|
||||
modules: 'common'
|
||||
},
|
||||
files: [
|
||||
{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/cjs/'
|
||||
}
|
||||
]
|
||||
}
|
||||
dest: 'dist/cjs/',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
webpack: {
|
||||
options: {
|
||||
context: __dirname,
|
||||
module: {
|
||||
loaders: [
|
||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader:
|
||||
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: 'dist/',
|
||||
library: 'Handlebars',
|
||||
libraryTarget: 'umd'
|
||||
}
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
},
|
||||
handlebars: {
|
||||
entry: './lib/handlebars.js',
|
||||
entry: './dist/cjs/handlebars.js',
|
||||
output: {
|
||||
filename: 'handlebars.js'
|
||||
}
|
||||
filename: 'handlebars.js',
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
entry: './lib/handlebars.runtime.js',
|
||||
entry: './dist/cjs/handlebars.runtime.js',
|
||||
output: {
|
||||
filename: 'handlebars.runtime.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
requirejs: {
|
||||
options: {
|
||||
optimize: 'none',
|
||||
baseUrl: 'dist/amd/'
|
||||
filename: 'handlebars.runtime.js',
|
||||
},
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
name: 'handlebars',
|
||||
out: 'dist/handlebars.amd.js'
|
||||
}
|
||||
},
|
||||
runtime: {
|
||||
options: {
|
||||
name: 'handlebars.runtime',
|
||||
out: 'dist/handlebars.runtime.amd.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/,
|
||||
},
|
||||
dist: {
|
||||
files: [
|
||||
@@ -141,19 +91,19 @@ module.exports = function(grunt) {
|
||||
expand: true,
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function(dest, src) {
|
||||
rename: function (dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
concat: {
|
||||
tests: {
|
||||
src: ['spec/!(require).js'],
|
||||
dest: 'tmp/tests.js'
|
||||
}
|
||||
dest: 'tmp/tests.js',
|
||||
},
|
||||
},
|
||||
|
||||
connect: {
|
||||
@@ -161,71 +111,27 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
base: '.',
|
||||
hostname: '*',
|
||||
port: 9999
|
||||
}
|
||||
}
|
||||
},
|
||||
'saucelabs-mocha': {
|
||||
all: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: [
|
||||
'http://localhost:9999/spec/?headless=true',
|
||||
'http://localhost:9999/spec/amd.html?headless=true'
|
||||
],
|
||||
detailedError: true,
|
||||
concurrency: 4,
|
||||
browsers: [
|
||||
{ browserName: 'chrome' },
|
||||
{ browserName: 'firefox', platform: 'Linux' },
|
||||
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
|
||||
// {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
|
||||
{
|
||||
browserName: 'internet explorer',
|
||||
version: 11,
|
||||
platform: 'Windows 8.1'
|
||||
},
|
||||
{
|
||||
browserName: 'internet explorer',
|
||||
version: 10,
|
||||
platform: 'Windows 8'
|
||||
}
|
||||
]
|
||||
}
|
||||
port: 9999,
|
||||
},
|
||||
},
|
||||
sanity: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: [
|
||||
'http://localhost:9999/spec/umd.html?headless=true',
|
||||
'http://localhost:9999/spec/amd-runtime.html?headless=true',
|
||||
'http://localhost:9999/spec/umd-runtime.html?headless=true'
|
||||
],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [{ browserName: 'chrome' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
bgShell: {
|
||||
shell: {
|
||||
integrationTests: {
|
||||
cmd: './integration-testing/run-integration-tests.sh',
|
||||
bg: false,
|
||||
fail: true
|
||||
}
|
||||
command: './tests/integration/run-integration-tests.sh',
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true
|
||||
atBegin: true,
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['on-file-change']
|
||||
}
|
||||
}
|
||||
tasks: ['on-file-change'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Load tasks from npm
|
||||
@@ -233,66 +139,38 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-bg-shell');
|
||||
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
this.registerTask(
|
||||
'build',
|
||||
'Builds a distributable version of the current project',
|
||||
['parser', 'node', 'globals']
|
||||
);
|
||||
|
||||
this.registerTask('node', ['babel:cjs']);
|
||||
this.registerTask('globals', ['webpack']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', [
|
||||
'amd',
|
||||
grunt.registerTask('node', ['babel:cjs']);
|
||||
grunt.registerTask('globals', ['webpack']);
|
||||
grunt.registerTask('release', 'Build final packages', [
|
||||
'uglify',
|
||||
'test:min',
|
||||
'copy:dist',
|
||||
'copy:components',
|
||||
'copy:cdnjs'
|
||||
]);
|
||||
|
||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||
|
||||
this.registerTask('test', ['test:bin', 'test:cov']);
|
||||
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
|
||||
if (process.env.SAUCE_ACCESS_KEY) {
|
||||
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
|
||||
} else {
|
||||
grunt.registerTask('sauce', []);
|
||||
}
|
||||
|
||||
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
|
||||
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
||||
'default',
|
||||
'bgShell:integrationTests',
|
||||
'sauce',
|
||||
'metrics',
|
||||
'publish-to-aws'
|
||||
]);
|
||||
grunt.registerTask('on-file-change', [
|
||||
'build',
|
||||
'amd',
|
||||
'concat:tests',
|
||||
'test'
|
||||
]);
|
||||
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
|
||||
|
||||
// === Primary tasks ===
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('prepare', ['build', 'concat:tests']);
|
||||
grunt.registerTask(
|
||||
'build',
|
||||
'Builds a distributable version of the current project',
|
||||
['node', 'globals']
|
||||
);
|
||||
grunt.registerTask('integration-tests', [
|
||||
'default',
|
||||
'bgShell:integrationTests'
|
||||
'shell:integrationTests',
|
||||
]);
|
||||
};
|
||||
|
||||
+53
-53
@@ -1,17 +1,18 @@
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://ci.appveyor.com/project/wycats/handlebars-js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||
[](https://www.npmjs.com/package/handlebars)
|
||||
[](https://www.npmjs.com/package/handlebars)
|
||||
[](https://bundlephobia.com/package/handlebars)
|
||||
[](https://packagephobia.com/result?p=handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
|
||||
Handlebars.js is an extension to the [Mustache templating
|
||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
||||
Handlebars.js and Mustache are both logicless templating languages that
|
||||
keep the view and the code separated like we all know they should be.
|
||||
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||
|
||||
Checkout the official Handlebars docs site at
|
||||
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
||||
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||
|
||||
Installing
|
||||
----------
|
||||
@@ -22,7 +23,7 @@ Usage
|
||||
-----
|
||||
In general, the syntax of Handlebars.js templates is a superset
|
||||
of Mustache templates. For basic syntax, check out the [Mustache
|
||||
manpage](http://mustache.github.com/mustache.5.html).
|
||||
manpage](https://mustache.github.io/mustache.5.html).
|
||||
|
||||
Once you have a template, use the `Handlebars.compile` method to compile
|
||||
the template into a function. The generated function takes a context
|
||||
@@ -64,32 +65,30 @@ templates easier and also changes a tiny detail of how partials work.
|
||||
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
||||
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
||||
|
||||
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||
- Handlebars does not allow space between the opening `{{` and a command character such as `#`, `/` or `>`. The command character must immediately follow the braces, so for example `{{> partial }}` is allowed but `{{ > partial }}` is not.
|
||||
- Alternative delimiters are not supported.
|
||||
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
|
||||
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
|
||||
Handlebars has been designed to work in any ECMAScript 2020 environment. This includes
|
||||
|
||||
- Node.js
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari 5+
|
||||
- Opera 11+
|
||||
- IE 6+
|
||||
- Safari
|
||||
- Edge
|
||||
|
||||
Older versions and other runtimes are likely to work but have not been formally
|
||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
||||
If you need to support older environments, use Handlebars version 4.
|
||||
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Performance
|
||||
-----------
|
||||
@@ -102,57 +101,58 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
||||
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
|
||||
If you are using Handlebars in production, please regularly look for issues labeled
|
||||
[possibly breaking](https://github.com/handlebars-lang/handlebars.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22possibly+breaking%22).
|
||||
If this label is applied to an issue, it means that the requested change is probably not a breaking change,
|
||||
but since Handlebars is widely in use by a lot of people, there's always a chance that it breaks somebody's build.
|
||||
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
|
||||
|
||||
Handlebars in the Wild
|
||||
----------------------
|
||||
|
||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
structure your views, also with automatic data binding support.
|
||||
* [apiDoc](https://github.com/apidoc/apidoc) apiDoc uses handlebars as parsing engine for api documentation view generation.
|
||||
* [Assemble](https://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js as its template engine.
|
||||
* [CoSchedule](https://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js.
|
||||
* [Ember.js](https://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
|
||||
* [express-handlebars](https://github.com/express-handlebars/express-handlebars) A Handlebars view engine for Express which doesn't suck.
|
||||
* [express-hbs](https://github.com/TryGhost/express-hbs) Express Handlebars template engine with inheritance, partials, i18n and async helpers.
|
||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem
|
||||
from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
||||
with 100+ handlebars helpers.
|
||||
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extendible and embeddable layout blocks as seen in other popular templating languages.
|
||||
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
||||
from Don Park.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
||||
renderer for Handlebars.js.
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
|
||||
for anyone who would like to try out Handlebars.js in their browser.
|
||||
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
|
||||
Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||
handlebars projects.
|
||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
||||
supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||
templating engine, extending it with automatic data binding support.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
|
||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
* [handlebars-action](https://github.com/marketplace/actions/handlebars-action) A GitHub action to transform files in your repository with Handlebars templating.
|
||||
* [handlebars_assets](https://github.com/leshill/handlebars_assets) A Rails Asset Pipeline gem from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library with 100+ handlebars helpers.
|
||||
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extensible and embeddable layout blocks as seen in other popular templating languages.
|
||||
* [handlebars-loader](https://github.com/pcardune/handlebars-loader) A handlebars template loader for webpack.
|
||||
* [handlebars-wax](https://github.com/shannonmoeller/handlebars-wax) The missing Handlebars API. Effortless registration of data, partials, helpers, and decorators using file-system globs, modules, and plain-old JavaScript objects.
|
||||
* [hbs](https://github.com/pillarjs/hbs) An Express.js view engine adapter for Handlebars.js, from Don Park.
|
||||
* [html-bundler-webpack-plugin](https://github.com/webdiscus/html-bundler-webpack-plugin) The webpack plugin to compile templates, [supports Handlebars](https://github.com/webdiscus/html-bundler-webpack-plugin#using-template-handlebars).
|
||||
* [incremental-bars](https://github.com/atomictag/incremental-bars) adds support for [incremental-dom](https://github.com/google/incremental-dom) as template target to Handlebars.
|
||||
* [jblotus](https://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would like to try out Handlebars.js in their browser.
|
||||
* [jQuery plugin](https://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs) [koa](https://github.com/koajs/koa) generator based renderer for Handlebars.js.
|
||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
* [openVALIDATION](https://github.com/openvalidation/openvalidation) a natural language compiler for validation rules. Generates program code in Java, JavaScript, C#, Python and Rust with handlebars.
|
||||
* [Plop](https://plopjs.com/) is a micro-generator framework that makes it easy to create files with a level of uniformity.
|
||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
* [sammy.js](https://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [SproutCore](https://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
|
||||
* [vite-plugin-handlebars](https://github.com/alexlafroscia/vite-plugin-handlebars) A package for Vite 2. Allows for running your HTML files through the Handlebars compiler.
|
||||
* [YUI](https://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars.
|
||||
|
||||
External Resources
|
||||
------------------
|
||||
@@ -165,4 +165,4 @@ License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
We recommend always using the latest versions of Handlebars and its official companion libraries to ensure your application remains as secure as possible.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 5.0.x | :white_check_mark: |
|
||||
| 4.7.x | :white_check_mark: |
|
||||
| < 4.7 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a vulnerability, please visit https://github.com/handlebars-lang/handlebars.js/security.
|
||||
@@ -1,36 +0,0 @@
|
||||
# Test against these versions of Node.js
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# Install scripts (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node.js
|
||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||
# Clone submodules (mustache spec)
|
||||
- cmd: git submodule update --init --recursive
|
||||
# Install modules
|
||||
- cmd: npm ci
|
||||
|
||||
|
||||
# Post-install test scripts
|
||||
test_script:
|
||||
# Output useful info for debugging
|
||||
- cmd: node --version
|
||||
- cmd: npm --version
|
||||
# Run tests
|
||||
- cmd: npm run test
|
||||
|
||||
# Don't actually build
|
||||
build: off
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a coverage.zip coverage
|
||||
- cmd: appveyor PushArtifact coverage.zip
|
||||
|
||||
|
||||
# Set build version format here instead of in the admin panel
|
||||
version: "{build}"
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"extends": "prettier",
|
||||
"globals": {
|
||||
"require": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0,
|
||||
"no-shadow": 0,
|
||||
"handle-callback-err": 0,
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<h1><%= @header() %></h1>
|
||||
<% if @items.length: %>
|
||||
<ul>
|
||||
<% for item in @items: %>
|
||||
<% if item.current: %>
|
||||
<li><strong><%= item.name %></strong></li>
|
||||
<% else: %>
|
||||
<li><a href="<%= item.url %>"><%= item.name %></a></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else: %>
|
||||
<p>The list is empty.</p>
|
||||
<% end %>
|
||||
@@ -5,103 +5,111 @@ const yargs = require('yargs')
|
||||
.option('f', {
|
||||
type: 'string',
|
||||
description: 'Output File',
|
||||
alias: 'output'
|
||||
alias: 'output',
|
||||
})
|
||||
.option('map', {
|
||||
type: 'string',
|
||||
description: 'Source Map File'
|
||||
description: 'Source Map File',
|
||||
})
|
||||
.option('a', {
|
||||
type: 'boolean',
|
||||
description: 'Exports amd style (require.js)',
|
||||
alias: 'amd'
|
||||
.option('esm', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exports ECMAScript module style, path to Handlebars module (eg. "handlebars/lib/handlebars")',
|
||||
default: null,
|
||||
})
|
||||
.option('c', {
|
||||
type: 'string',
|
||||
description: 'Exports CommonJS style, path to Handlebars module',
|
||||
alias: 'commonjs',
|
||||
default: null
|
||||
default: null,
|
||||
})
|
||||
.option('a', {
|
||||
type: 'boolean',
|
||||
description: 'Exports AMD style (require.js)',
|
||||
alias: 'amd',
|
||||
deprecated: true,
|
||||
})
|
||||
.option('h', {
|
||||
type: 'string',
|
||||
description: 'Path to handlebar.js (only valid for amd-style)',
|
||||
description: 'Path to handlebar.js (only valid for AMD-style)',
|
||||
alias: 'handlebarPath',
|
||||
default: ''
|
||||
default: '',
|
||||
deprecated: true,
|
||||
})
|
||||
.option('k', {
|
||||
type: 'string',
|
||||
description: 'Known helpers',
|
||||
alias: 'known'
|
||||
alias: 'known',
|
||||
})
|
||||
.option('o', {
|
||||
type: 'boolean',
|
||||
description: 'Known helpers only',
|
||||
alias: 'knownOnly'
|
||||
alias: 'knownOnly',
|
||||
})
|
||||
.option('m', {
|
||||
type: 'boolean',
|
||||
description: 'Minimize output',
|
||||
alias: 'min'
|
||||
alias: 'min',
|
||||
})
|
||||
.option('n', {
|
||||
type: 'string',
|
||||
description: 'Template namespace',
|
||||
alias: 'namespace',
|
||||
default: 'Handlebars.templates'
|
||||
default: 'Handlebars.templates',
|
||||
})
|
||||
.option('s', {
|
||||
type: 'boolean',
|
||||
description: 'Output template function only.',
|
||||
alias: 'simple'
|
||||
alias: 'simple',
|
||||
})
|
||||
.option('N', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||
alias: 'name'
|
||||
alias: 'name',
|
||||
})
|
||||
.option('i', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
|
||||
alias: 'string'
|
||||
alias: 'string',
|
||||
})
|
||||
.option('r', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Template root. Base value that will be stripped from template names.',
|
||||
alias: 'root'
|
||||
alias: 'root',
|
||||
})
|
||||
.option('p', {
|
||||
type: 'boolean',
|
||||
description: 'Compiling a partial template',
|
||||
alias: 'partial'
|
||||
alias: 'partial',
|
||||
})
|
||||
.option('d', {
|
||||
type: 'boolean',
|
||||
description: 'Include data when compiling',
|
||||
alias: 'data'
|
||||
alias: 'data',
|
||||
})
|
||||
.option('e', {
|
||||
type: 'string',
|
||||
description: 'Template extension.',
|
||||
alias: 'extension',
|
||||
default: 'handlebars'
|
||||
default: 'handlebars',
|
||||
})
|
||||
.option('b', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||
alias: 'bom'
|
||||
alias: 'bom',
|
||||
})
|
||||
.option('v', {
|
||||
type: 'boolean',
|
||||
description: 'Prints the current compiler version',
|
||||
alias: 'version'
|
||||
alias: 'version',
|
||||
})
|
||||
.option('help', {
|
||||
type: 'boolean',
|
||||
description: 'Outputs this message'
|
||||
description: 'Outputs this message',
|
||||
})
|
||||
.wrap(120);
|
||||
|
||||
@@ -110,8 +118,7 @@ argv.files = argv._;
|
||||
delete argv._;
|
||||
|
||||
const Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||
|
||||
Precompiler.loadTemplates(argv, function (err, opts) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"main": "handlebars.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
"repo": "components/handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "handlebars.js",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
]
|
||||
"scripts": ["handlebars.js"]
|
||||
}
|
||||
|
||||
+32
-32
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||
gem.version = package["version"].sub "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>4.7.4</version>
|
||||
<version>5.0.0-alpha.1</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Extension of the Mustache logicless template language</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"license": "MIT",
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
|
||||
+32
-31
@@ -34,8 +34,8 @@ let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||
|
||||
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||
|
||||
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||
- Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||
- Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||
|
||||
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||
|
||||
@@ -132,7 +132,6 @@ interface PartialBlockStatement <: Statement {
|
||||
|
||||
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
|
||||
|
||||
|
||||
```java
|
||||
interface ContentStatement <: Statement {
|
||||
type: "ContentStatement";
|
||||
@@ -148,7 +147,6 @@ interface CommentStatement <: Statement {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```java
|
||||
interface Decorator <: Statement {
|
||||
type: "Decorator";
|
||||
@@ -209,7 +207,6 @@ interface PathExpression <: Expression {
|
||||
- `parts` is an array of the names in the path. `foo.bar` would be `['foo', 'bar']`. Scope references, `.`, `..`, and `this` should be omitted from this array.
|
||||
- `original` is the path as entered by the user. Separator and scope references are left untouched.
|
||||
|
||||
|
||||
##### Literals
|
||||
|
||||
```java
|
||||
@@ -242,7 +239,6 @@ interface NullLiteral <: Literal {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
```java
|
||||
@@ -279,8 +275,8 @@ function ImportScanner() {
|
||||
}
|
||||
ImportScanner.prototype = new Visitor();
|
||||
|
||||
ImportScanner.prototype.PartialStatement = function(partial) {
|
||||
this.partials.push({request: partial.name.original});
|
||||
ImportScanner.prototype.PartialStatement = function (partial) {
|
||||
this.partials.push({ request: partial.name.original });
|
||||
|
||||
Visitor.prototype.PartialStatement.call(this, partial);
|
||||
};
|
||||
@@ -291,7 +287,7 @@ scanner.accept(ast);
|
||||
|
||||
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
|
||||
|
||||
The visitor may also be configured to operate in mutation mode by setting the `mutation` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node in tact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
|
||||
The visitor may also be configured to operate in mutation mode by setting the `mutating` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node intact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
|
||||
|
||||
Implementors that may need to support mutation mode are encouraged to utilize the `acceptKey`, `acceptRequired` and `acceptArray` helpers which provide the conditional overwrite behavior as well as implement sanity checks where pertinent.
|
||||
|
||||
@@ -300,7 +296,7 @@ Implementors that may need to support mutation mode are encouraged to utilize th
|
||||
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the compiler:
|
||||
|
||||
- `nameLookup(parent, name, type)`
|
||||
Used to generate the code to resolve a give path component.
|
||||
Used to generate the code to resolve a given path component.
|
||||
|
||||
- `parent` is the existing code in the path resolution
|
||||
- `name` is the current path component
|
||||
@@ -315,14 +311,14 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
Allows for custom compiler flags used in the runtime version checking logic.
|
||||
|
||||
- `appendToBuffer(source, location, explicit)`
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
|
||||
- `initializeBuffer()`
|
||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||
|
||||
### Example for the compiler api.
|
||||
|
||||
@@ -330,7 +326,6 @@ This example changes all lookups of properties are performed by a helper (`looku
|
||||
|
||||
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
|
||||
|
||||
|
||||
```javascript
|
||||
function MyCompiler() {
|
||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||
@@ -338,29 +333,35 @@ function MyCompiler() {
|
||||
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
||||
|
||||
// Use this compile to compile BlockStatment-Blocks
|
||||
MyCompiler.prototype.compiler = MyCompiler
|
||||
MyCompiler.prototype.compiler = MyCompiler;
|
||||
|
||||
MyCompiler.prototype.nameLookup = function(parent, name, type) {
|
||||
MyCompiler.prototype.nameLookup = function (parent, name, type) {
|
||||
if (type === 'context') {
|
||||
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
|
||||
return this.source.functionCall('helpers.lookupLowerCase', '', [
|
||||
parent,
|
||||
JSON.stringify(name),
|
||||
]);
|
||||
} else {
|
||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(
|
||||
this,
|
||||
parent,
|
||||
name,
|
||||
type
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var env = Handlebars.create();
|
||||
env.registerHelper('lookupLowerCase', function(parent, name) {
|
||||
return parent[name.toLowerCase()]
|
||||
})
|
||||
env.registerHelper('lookupLowerCase', function (parent, name) {
|
||||
return parent[name.toLowerCase()];
|
||||
});
|
||||
|
||||
env.JavaScriptCompiler = MyCompiler;
|
||||
|
||||
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
||||
console.log(template({
|
||||
test: [
|
||||
{value: 'a'},
|
||||
{value: 'b'},
|
||||
{value: 'c'}
|
||||
]
|
||||
}));
|
||||
console.log(
|
||||
template({
|
||||
test: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Decorators
|
||||
|
||||
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to setup a particular state in the system prior to running the block.
|
||||
**Decorators are deprecated, please join the discussion at [#1574](https://github.com/handlebars-lang/handlebars.js/issues/1574) to see what we can do about it.**
|
||||
|
||||
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to set up a particular state in the system prior to running the block.
|
||||
|
||||
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
|
||||
|
||||
@@ -16,4 +18,4 @@ Decorators are executed when the block program is instantiated and are passed `(
|
||||
|
||||
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||
|
||||
The [inline partial](https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'plugin:es5/no-es2015', 'prettier'],
|
||||
globals: {
|
||||
self: false
|
||||
},
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-var': 'off'
|
||||
}
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "webpack-test",
|
||||
"description": "Various tests with Handlebars and Webpack",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"test": "node dist/main.js"
|
||||
},
|
||||
"private": true,
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"handlebars": "file:../..",
|
||||
"handlebars-loader": "^1.7.1",
|
||||
"webpack": "^4.39.3",
|
||||
"webpack-cli": "^3.3.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
env: {
|
||||
// Handlebars should run natively in the browser
|
||||
node: false,
|
||||
},
|
||||
};
|
||||
+9
-8
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
parser as Parser,
|
||||
parse,
|
||||
parseWithoutProcessing,
|
||||
Visitor,
|
||||
} from '@handlebars/parser';
|
||||
|
||||
import runtime from './handlebars.runtime';
|
||||
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import {
|
||||
parser as Parser,
|
||||
parse,
|
||||
parseWithoutProcessing
|
||||
} from './handlebars/compiler/base';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import Visitor from './handlebars/compiler/visitor';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
|
||||
@@ -17,10 +18,10 @@ let _create = runtime.create;
|
||||
function create() {
|
||||
let hb = _create();
|
||||
|
||||
hb.compile = function(input, options) {
|
||||
hb.compile = function (input, options) {
|
||||
return compile(input, options, hb);
|
||||
};
|
||||
hb.precompile = function(input, options) {
|
||||
hb.precompile = function (input, options) {
|
||||
return precompile(input, options, hb);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as base from './handlebars/base';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import Exception from './handlebars/exception';
|
||||
import * as Utils from './handlebars/utils';
|
||||
import * as runtime from './handlebars/runtime';
|
||||
|
||||
@@ -20,7 +20,7 @@ function create() {
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
hb.template = function (spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, extend, toString } from './utils';
|
||||
import Exception from './exception';
|
||||
import { registerDefaultHelpers } from './helpers';
|
||||
import { registerDefaultDecorators } from './decorators';
|
||||
import logger from './logger';
|
||||
import { resetLoggedProperties } from './internal/proto-access';
|
||||
|
||||
export const VERSION = '4.7.4';
|
||||
export const VERSION = '4.7.7';
|
||||
export const COMPILER_REVISION = 8;
|
||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||
|
||||
@@ -17,7 +17,7 @@ export const REVISION_CHANGES = {
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1',
|
||||
7: '>= 4.0.0 <4.3.0',
|
||||
8: '>= 4.3.0'
|
||||
8: '>= 4.3.0',
|
||||
};
|
||||
|
||||
const objectType = '[object Object]';
|
||||
@@ -37,7 +37,7 @@ HandlebarsEnvironment.prototype = {
|
||||
logger: logger,
|
||||
log: logger.log,
|
||||
|
||||
registerHelper: function(name, fn) {
|
||||
registerHelper: function (name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple helpers');
|
||||
@@ -47,11 +47,11 @@ HandlebarsEnvironment.prototype = {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterHelper: function(name) {
|
||||
unregisterHelper: function (name) {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
registerPartial: function (name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
extend(this.partials, name);
|
||||
} else {
|
||||
@@ -63,11 +63,11 @@ HandlebarsEnvironment.prototype = {
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
unregisterPartial: function (name) {
|
||||
delete this.partials[name];
|
||||
},
|
||||
|
||||
registerDecorator: function(name, fn) {
|
||||
registerDecorator: function (name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple decorators');
|
||||
@@ -77,7 +77,7 @@ HandlebarsEnvironment.prototype = {
|
||||
this.decorators[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterDecorator: function(name) {
|
||||
unregisterDecorator: function (name) {
|
||||
delete this.decorators[name];
|
||||
},
|
||||
/**
|
||||
@@ -86,7 +86,7 @@ HandlebarsEnvironment.prototype = {
|
||||
*/
|
||||
resetLoggedPropertyAccesses() {
|
||||
resetLoggedProperties();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export let log = logger.log;
|
||||
|
||||
@@ -4,7 +4,7 @@ let AST = {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
helperExpression: function(node) {
|
||||
helperExpression: function (node) {
|
||||
return (
|
||||
node.type === 'SubExpression' ||
|
||||
((node.type === 'MustacheStatement' ||
|
||||
@@ -13,18 +13,18 @@ let AST = {
|
||||
);
|
||||
},
|
||||
|
||||
scopedId: function(path) {
|
||||
scopedId: function (path) {
|
||||
return /^\.|this\b/.test(path.original);
|
||||
},
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
simpleId: function(path) {
|
||||
simpleId: function (path) {
|
||||
return (
|
||||
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import parser from './parser';
|
||||
import WhitespaceControl from './whitespace-control';
|
||||
import * as Helpers from './helpers';
|
||||
import { extend } from '../utils';
|
||||
|
||||
export { parser };
|
||||
|
||||
let yy = {};
|
||||
extend(yy, Helpers);
|
||||
|
||||
export function parseWithoutProcessing(input, options) {
|
||||
// Just return if an already-compiled AST was passed in.
|
||||
if (input.type === 'Program') {
|
||||
return input;
|
||||
}
|
||||
|
||||
parser.yy = yy;
|
||||
|
||||
// Altering the shared object here, but this is ok as parser is a sync operation
|
||||
yy.locInfo = function(locInfo) {
|
||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||
};
|
||||
|
||||
let ast = parser.parse(input);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function parse(input, options) {
|
||||
let ast = parseWithoutProcessing(input, options);
|
||||
let strip = new WhitespaceControl(options);
|
||||
|
||||
return strip.accept(ast);
|
||||
}
|
||||
@@ -6,9 +6,9 @@ let SourceNode;
|
||||
try {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
// We don't support this in AMD environments. For these environments, we asusme that
|
||||
// We don't support this in AMD environments. For these environments, we assume that
|
||||
// they are running on the browser and thus have no need for the source-map library.
|
||||
let SourceMap = require('source-map');
|
||||
let SourceMap = require('source-map'); // eslint-disable-line no-undef
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -17,7 +17,7 @@ try {
|
||||
|
||||
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
|
||||
if (!SourceNode) {
|
||||
SourceNode = function(line, column, srcFile, chunks) {
|
||||
SourceNode = function (line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
@@ -25,24 +25,24 @@ if (!SourceNode) {
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function(chunks) {
|
||||
add: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function(chunks) {
|
||||
prepend: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function() {
|
||||
toStringWithSourceMap: function () {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString: function() {
|
||||
toString: function () {
|
||||
return this.src;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,32 +70,32 @@ CodeGen.prototype = {
|
||||
isEmpty() {
|
||||
return !this.source.length;
|
||||
},
|
||||
prepend: function(source, loc) {
|
||||
prepend: function (source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
push: function(source, loc) {
|
||||
push: function (source, loc) {
|
||||
this.source.push(this.wrap(source, loc));
|
||||
},
|
||||
|
||||
merge: function() {
|
||||
merge: function () {
|
||||
let source = this.empty();
|
||||
this.each(function(line) {
|
||||
this.each(function (line) {
|
||||
source.add([' ', line, '\n']);
|
||||
});
|
||||
return source;
|
||||
},
|
||||
|
||||
each: function(iter) {
|
||||
each: function (iter) {
|
||||
for (let i = 0, len = this.source.length; i < len; i++) {
|
||||
iter(this.source[i]);
|
||||
}
|
||||
},
|
||||
|
||||
empty: function() {
|
||||
empty: function () {
|
||||
let loc = this.currentLocation || { start: {} };
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
|
||||
wrap: function (chunk, loc = this.currentLocation || { start: {} }) {
|
||||
if (chunk instanceof SourceNode) {
|
||||
return chunk;
|
||||
}
|
||||
@@ -110,12 +110,12 @@ CodeGen.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
functionCall: function(fn, type, params) {
|
||||
functionCall: function (fn, type, params) {
|
||||
params = this.generateList(params);
|
||||
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
quotedString: function (str) {
|
||||
return (
|
||||
'"' +
|
||||
(str + '')
|
||||
@@ -129,10 +129,10 @@ CodeGen.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
objectLiteral: function (obj) {
|
||||
let pairs = [];
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
@@ -145,7 +145,7 @@ CodeGen.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateList: function(entries) {
|
||||
generateList: function (entries) {
|
||||
let ret = this.empty();
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
@@ -159,13 +159,13 @@ CodeGen.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries) {
|
||||
generateArray: function (entries) {
|
||||
let ret = this.generateList(entries);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default CodeGen;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isArray, indexOf, extend } from '../utils';
|
||||
import AST from './ast';
|
||||
|
||||
@@ -16,7 +16,7 @@ export function Compiler() {}
|
||||
Compiler.prototype = {
|
||||
compiler: Compiler,
|
||||
|
||||
equals: function(other) {
|
||||
equals: function (other) {
|
||||
let len = this.opcodes.length;
|
||||
if (other.opcodes.length !== len) {
|
||||
return false;
|
||||
@@ -47,13 +47,11 @@ Compiler.prototype = {
|
||||
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
compile: function (program, options) {
|
||||
this.sourceNode = [];
|
||||
this.opcodes = [];
|
||||
this.children = [];
|
||||
this.options = options;
|
||||
this.stringParams = options.stringParams;
|
||||
this.trackIds = options.trackIds;
|
||||
|
||||
options.blockParams = options.blockParams || [];
|
||||
|
||||
@@ -67,7 +65,7 @@ Compiler.prototype = {
|
||||
unless: true,
|
||||
with: true,
|
||||
log: true,
|
||||
lookup: true
|
||||
lookup: true,
|
||||
},
|
||||
options.knownHelpers
|
||||
);
|
||||
@@ -75,7 +73,7 @@ Compiler.prototype = {
|
||||
return this.accept(program);
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
compileProgram: function (program) {
|
||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
@@ -88,7 +86,7 @@ Compiler.prototype = {
|
||||
return guid;
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
accept: function (node) {
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[node.type]) {
|
||||
throw new Exception('Unknown type: ' + node.type, node);
|
||||
@@ -100,7 +98,7 @@ Compiler.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
Program: function(program) {
|
||||
Program: function (program) {
|
||||
this.options.blockParams.unshift(program.blockParams);
|
||||
|
||||
let body = program.body,
|
||||
@@ -117,7 +115,7 @@ Compiler.prototype = {
|
||||
return this;
|
||||
},
|
||||
|
||||
BlockStatement: function(block) {
|
||||
BlockStatement: function (block) {
|
||||
transformLiteralToPath(block);
|
||||
|
||||
let program = block.program,
|
||||
@@ -162,7 +160,7 @@ Compiler.prototype = {
|
||||
this.opcode('registerDecorator', params.length, path.original);
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
PartialStatement: function (partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
let program = partial.program;
|
||||
@@ -201,11 +199,11 @@ Compiler.prototype = {
|
||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||
this.opcode('append');
|
||||
},
|
||||
PartialBlockStatement: function(partialBlock) {
|
||||
PartialBlockStatement: function (partialBlock) {
|
||||
this.PartialStatement(partialBlock);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
MustacheStatement: function (mustache) {
|
||||
this.SubExpression(mustache);
|
||||
|
||||
if (mustache.escaped && !this.options.noEscape) {
|
||||
@@ -218,15 +216,15 @@ Compiler.prototype = {
|
||||
this.DecoratorBlock(decorator);
|
||||
},
|
||||
|
||||
ContentStatement: function(content) {
|
||||
ContentStatement: function (content) {
|
||||
if (content.value) {
|
||||
this.opcode('appendContent', content.value);
|
||||
}
|
||||
},
|
||||
|
||||
CommentStatement: function() {},
|
||||
CommentStatement: function () {},
|
||||
|
||||
SubExpression: function(sexpr) {
|
||||
SubExpression: function (sexpr) {
|
||||
transformLiteralToPath(sexpr);
|
||||
let type = this.classifySexpr(sexpr);
|
||||
|
||||
@@ -238,7 +236,7 @@ Compiler.prototype = {
|
||||
this.ambiguousSexpr(sexpr);
|
||||
}
|
||||
},
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
ambiguousSexpr: function (sexpr, program, inverse) {
|
||||
let path = sexpr.path,
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
@@ -254,14 +252,14 @@ Compiler.prototype = {
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
simpleSexpr: function (sexpr) {
|
||||
let path = sexpr.path;
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
helperSexpr: function (sexpr, program, inverse) {
|
||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
@@ -287,7 +285,7 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
PathExpression: function(path) {
|
||||
PathExpression: function (path) {
|
||||
this.addDepth(path.depth);
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
@@ -314,27 +312,27 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
StringLiteral: function(string) {
|
||||
StringLiteral: function (string) {
|
||||
this.opcode('pushString', string.value);
|
||||
},
|
||||
|
||||
NumberLiteral: function(number) {
|
||||
NumberLiteral: function (number) {
|
||||
this.opcode('pushLiteral', number.value);
|
||||
},
|
||||
|
||||
BooleanLiteral: function(bool) {
|
||||
BooleanLiteral: function (bool) {
|
||||
this.opcode('pushLiteral', bool.value);
|
||||
},
|
||||
|
||||
UndefinedLiteral: function() {
|
||||
UndefinedLiteral: function () {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
},
|
||||
|
||||
NullLiteral: function() {
|
||||
NullLiteral: function () {
|
||||
this.opcode('pushLiteral', 'null');
|
||||
},
|
||||
|
||||
Hash: function(hash) {
|
||||
Hash: function (hash) {
|
||||
let pairs = hash.pairs,
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
@@ -351,15 +349,15 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
opcode: function(name) {
|
||||
opcode: function (name) {
|
||||
this.opcodes.push({
|
||||
opcode: name,
|
||||
args: slice.call(arguments, 1),
|
||||
loc: this.sourceNode[0].loc
|
||||
loc: this.sourceNode[0].loc,
|
||||
});
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
addDepth: function (depth) {
|
||||
if (!depth) {
|
||||
return;
|
||||
}
|
||||
@@ -367,7 +365,7 @@ Compiler.prototype = {
|
||||
this.useDepths = true;
|
||||
},
|
||||
|
||||
classifySexpr: function(sexpr) {
|
||||
classifySexpr: function (sexpr) {
|
||||
let isSimple = AST.helpers.simpleId(sexpr.path);
|
||||
|
||||
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
||||
@@ -402,57 +400,17 @@ Compiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
pushParams: function(params) {
|
||||
pushParams: function (params) {
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
this.pushParam(params[i]);
|
||||
}
|
||||
},
|
||||
|
||||
pushParam: function(val) {
|
||||
let value = val.value != null ? val.value : val.original || '';
|
||||
|
||||
if (this.stringParams) {
|
||||
if (value.replace) {
|
||||
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||
}
|
||||
|
||||
if (val.depth) {
|
||||
this.addDepth(val.depth);
|
||||
}
|
||||
this.opcode('getContext', val.depth || 0);
|
||||
this.opcode('pushStringParam', value, val.type);
|
||||
|
||||
if (val.type === 'SubExpression') {
|
||||
// SubExpressions get evaluated and passed in
|
||||
// in string params mode.
|
||||
this.accept(val);
|
||||
}
|
||||
} else {
|
||||
if (this.trackIds) {
|
||||
let blockParamIndex;
|
||||
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||
}
|
||||
if (blockParamIndex) {
|
||||
let blockParamChild = val.parts.slice(1).join('.');
|
||||
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
|
||||
} else {
|
||||
value = val.original || value;
|
||||
if (value.replace) {
|
||||
value = value
|
||||
.replace(/^this(?:\.|$)/, '')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/^\.$/, '');
|
||||
}
|
||||
|
||||
this.opcode('pushId', val.type, value);
|
||||
}
|
||||
}
|
||||
this.accept(val);
|
||||
}
|
||||
pushParam: function (val) {
|
||||
this.accept(val);
|
||||
},
|
||||
|
||||
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
|
||||
setupFullMustacheParams: function (sexpr, program, inverse, omitEmpty) {
|
||||
let params = sexpr.params;
|
||||
this.pushParams(params);
|
||||
|
||||
@@ -468,7 +426,7 @@ Compiler.prototype = {
|
||||
return params;
|
||||
},
|
||||
|
||||
blockParamIndex: function(name) {
|
||||
blockParamIndex: function (name) {
|
||||
for (
|
||||
let depth = 0, len = this.options.blockParams.length;
|
||||
depth < len;
|
||||
@@ -480,57 +438,25 @@ Compiler.prototype = {
|
||||
return [depth, param];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export function precompile(input, options, env) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
|
||||
input
|
||||
);
|
||||
}
|
||||
export function precompile(input, options = {}, env) {
|
||||
validateInput(input, options);
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options);
|
||||
let environment = compileEnvironment(input, options, env);
|
||||
return new env.JavaScriptCompiler().compile(environment, options);
|
||||
}
|
||||
|
||||
export function compile(input, options = {}, env) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||
input
|
||||
);
|
||||
}
|
||||
|
||||
options = extend({}, options);
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
validateInput(input, options);
|
||||
|
||||
let compiled;
|
||||
|
||||
function compileInput() {
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options),
|
||||
let environment = compileEnvironment(input, options, env),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
@@ -541,25 +467,41 @@ export function compile(input, options = {}, env) {
|
||||
}
|
||||
|
||||
// Template is only compiled on first use and cached after that point.
|
||||
function ret(context, execOptions) {
|
||||
return function (context, execOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled.call(this, context, execOptions);
|
||||
};
|
||||
}
|
||||
|
||||
function validateInput(input, options) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||
input
|
||||
);
|
||||
}
|
||||
ret._setup = function(setupOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._setup(setupOptions);
|
||||
};
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._child(i, data, blockParams, depths);
|
||||
};
|
||||
return ret;
|
||||
|
||||
if (options.trackIds || options.stringParams) {
|
||||
throw new Exception(
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
}
|
||||
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
}
|
||||
function compileEnvironment(input, options, env) {
|
||||
let ast = env.parse(input, options);
|
||||
return new env.Compiler().compile(ast, options);
|
||||
}
|
||||
|
||||
function argEquals(a, b) {
|
||||
@@ -588,7 +530,7 @@ function transformLiteralToPath(sexpr) {
|
||||
depth: 0,
|
||||
parts: [literal.original + ''],
|
||||
original: literal.original + '',
|
||||
loc: literal.loc
|
||||
loc: literal.loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
function validateClose(open, close) {
|
||||
close = close.path ? close.path.original : close;
|
||||
|
||||
if (open.path.original !== close) {
|
||||
let errorNode = { loc: open.path.loc };
|
||||
|
||||
throw new Exception(
|
||||
open.path.original + " doesn't match " + close,
|
||||
errorNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function SourceLocation(source, locInfo) {
|
||||
this.source = source;
|
||||
this.start = {
|
||||
line: locInfo.first_line,
|
||||
column: locInfo.first_column
|
||||
};
|
||||
this.end = {
|
||||
line: locInfo.last_line,
|
||||
column: locInfo.last_column
|
||||
};
|
||||
}
|
||||
|
||||
export function id(token) {
|
||||
if (/^\[.*\]$/.test(token)) {
|
||||
return token.substring(1, token.length - 1);
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
export function stripFlags(open, close) {
|
||||
return {
|
||||
open: open.charAt(2) === '~',
|
||||
close: close.charAt(close.length - 3) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
export function stripComment(comment) {
|
||||
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
|
||||
}
|
||||
|
||||
export function preparePath(data, parts, loc) {
|
||||
loc = this.locInfo(loc);
|
||||
|
||||
let original = data ? '@' : '',
|
||||
dig = [],
|
||||
depth = 0;
|
||||
|
||||
for (let i = 0, l = parts.length; i < l; i++) {
|
||||
let part = parts[i].part,
|
||||
// If we have [] syntax then we do not treat path references as operators,
|
||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||
isLiteral = parts[i].original !== part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||
if (dig.length > 0) {
|
||||
throw new Exception('Invalid path: ' + original, { loc });
|
||||
} else if (part === '..') {
|
||||
depth++;
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'PathExpression',
|
||||
data,
|
||||
depth,
|
||||
parts: dig,
|
||||
original,
|
||||
loc
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
// Must use charAt to support IE pre-10
|
||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
let decorator = /\*/.test(open);
|
||||
return {
|
||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||
path,
|
||||
params,
|
||||
hash,
|
||||
escaped,
|
||||
strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
||||
validateClose(openRawBlock, close);
|
||||
|
||||
locInfo = this.locInfo(locInfo);
|
||||
let program = {
|
||||
type: 'Program',
|
||||
body: contents,
|
||||
strip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'BlockStatement',
|
||||
path: openRawBlock.path,
|
||||
params: openRawBlock.params,
|
||||
hash: openRawBlock.hash,
|
||||
program,
|
||||
openStrip: {},
|
||||
inverseStrip: {},
|
||||
closeStrip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareBlock(
|
||||
openBlock,
|
||||
program,
|
||||
inverseAndProgram,
|
||||
close,
|
||||
inverted,
|
||||
locInfo
|
||||
) {
|
||||
if (close && close.path) {
|
||||
validateClose(openBlock, close);
|
||||
}
|
||||
|
||||
let decorator = /\*/.test(openBlock.open);
|
||||
|
||||
program.blockParams = openBlock.blockParams;
|
||||
|
||||
let inverse, inverseStrip;
|
||||
|
||||
if (inverseAndProgram) {
|
||||
if (decorator) {
|
||||
throw new Exception(
|
||||
'Unexpected inverse block on decorator',
|
||||
inverseAndProgram
|
||||
);
|
||||
}
|
||||
|
||||
if (inverseAndProgram.chain) {
|
||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||
}
|
||||
|
||||
inverseStrip = inverseAndProgram.strip;
|
||||
inverse = inverseAndProgram.program;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
inverted = inverse;
|
||||
inverse = program;
|
||||
program = inverted;
|
||||
}
|
||||
|
||||
return {
|
||||
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
|
||||
path: openBlock.path,
|
||||
params: openBlock.params,
|
||||
hash: openBlock.hash,
|
||||
program,
|
||||
inverse,
|
||||
openStrip: openBlock.strip,
|
||||
inverseStrip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareProgram(statements, loc) {
|
||||
if (!loc && statements.length) {
|
||||
const firstLoc = statements[0].loc,
|
||||
lastLoc = statements[statements.length - 1].loc;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (firstLoc && lastLoc) {
|
||||
loc = {
|
||||
source: firstLoc.source,
|
||||
start: {
|
||||
line: firstLoc.start.line,
|
||||
column: firstLoc.start.column
|
||||
},
|
||||
end: {
|
||||
line: lastLoc.end.line,
|
||||
column: lastLoc.end.column
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
body: statements,
|
||||
strip: {},
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
export function preparePartialBlock(open, program, close, locInfo) {
|
||||
validateClose(open, close);
|
||||
|
||||
return {
|
||||
type: 'PartialBlockStatement',
|
||||
name: open.path,
|
||||
params: open.params,
|
||||
hash: open.hash,
|
||||
program,
|
||||
openStrip: open.strip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||
import Exception from '../exception';
|
||||
import { isArray } from '../utils';
|
||||
import CodeGen from './code-gen';
|
||||
|
||||
@@ -12,20 +12,25 @@ function JavaScriptCompiler() {}
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /*, type */) {
|
||||
nameLookup: function (parent, name /*, type */) {
|
||||
return this.internalNameLookup(parent, name);
|
||||
},
|
||||
depthedLookup: function(name) {
|
||||
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
|
||||
depthedLookup: function (name) {
|
||||
return [
|
||||
this.aliasable('container.lookup'),
|
||||
'(depths, ',
|
||||
JSON.stringify(name),
|
||||
')',
|
||||
];
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
compilerInfo: function () {
|
||||
const revision = COMPILER_REVISION,
|
||||
versions = REVISION_CHANGES[revision];
|
||||
return [revision, versions];
|
||||
},
|
||||
|
||||
appendToBuffer: function(source, location, explicit) {
|
||||
appendToBuffer: function (source, location, explicit) {
|
||||
// Force a source as this simplifies the merge logic.
|
||||
if (!isArray(source)) {
|
||||
source = [source];
|
||||
@@ -45,22 +50,20 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
initializeBuffer: function() {
|
||||
initializeBuffer: function () {
|
||||
return this.quotedString('');
|
||||
},
|
||||
// END PUBLIC API
|
||||
internalNameLookup: function(parent, name) {
|
||||
internalNameLookup: function (parent, name) {
|
||||
this.lookupPropertyFunctionIsUsed = true;
|
||||
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
|
||||
},
|
||||
|
||||
lookupPropertyFunctionIsUsed: false,
|
||||
|
||||
compile: function(environment, options, context, asObject) {
|
||||
compile: function (environment, options, context, asObject) {
|
||||
this.environment = environment;
|
||||
this.options = options;
|
||||
this.stringParams = this.options.stringParams;
|
||||
this.trackIds = this.options.trackIds;
|
||||
this.precompile = !asObject;
|
||||
|
||||
this.name = this.environment.name;
|
||||
@@ -68,7 +71,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.context = context || {
|
||||
decorators: [],
|
||||
programs: [],
|
||||
environments: []
|
||||
environments: [],
|
||||
};
|
||||
|
||||
this.preamble();
|
||||
@@ -120,11 +123,12 @@ JavaScriptCompiler.prototype = {
|
||||
this.decorators.prepend([
|
||||
'var decorators = container.decorators, ',
|
||||
this.lookupPropertyFunctionVarDeclaration(),
|
||||
';\n'
|
||||
';\n',
|
||||
]);
|
||||
this.decorators.push('return fn;');
|
||||
|
||||
if (asObject) {
|
||||
// eslint-disable-next-line no-new-func
|
||||
this.decorators = Function.apply(this, [
|
||||
'fn',
|
||||
'props',
|
||||
@@ -133,7 +137,7 @@ JavaScriptCompiler.prototype = {
|
||||
'data',
|
||||
'blockParams',
|
||||
'depths',
|
||||
this.decorators.merge()
|
||||
this.decorators.merge(),
|
||||
]);
|
||||
} else {
|
||||
this.decorators.prepend(
|
||||
@@ -150,7 +154,7 @@ JavaScriptCompiler.prototype = {
|
||||
if (!this.isChild) {
|
||||
let ret = {
|
||||
compiler: this.compilerInfo(),
|
||||
main: fn
|
||||
main: fn,
|
||||
};
|
||||
|
||||
if (this.decorators) {
|
||||
@@ -207,7 +211,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
preamble: function () {
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
@@ -215,7 +219,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.decorators = new CodeGen(this.options.srcName);
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
createFunctionContext: function (asObject) {
|
||||
let varDeclarations = '';
|
||||
|
||||
let locals = this.stackVars.concat(this.registers.list);
|
||||
@@ -230,7 +234,7 @@ JavaScriptCompiler.prototype = {
|
||||
// aliases will not be used, but this case is already being run on the client and
|
||||
// we aren't concern about minimizing the template size.
|
||||
let aliasCount = 0;
|
||||
Object.keys(this.aliases).forEach(alias => {
|
||||
Object.keys(this.aliases).forEach((alias) => {
|
||||
let node = this.aliases[alias];
|
||||
if (node.children && node.referenceCount > 1) {
|
||||
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
|
||||
@@ -257,25 +261,25 @@ JavaScriptCompiler.prototype = {
|
||||
if (asObject) {
|
||||
params.push(source);
|
||||
|
||||
return Function.apply(this, params);
|
||||
return Function.apply(this, params); // eslint-disable-line no-new-func
|
||||
} else {
|
||||
return this.source.wrap([
|
||||
'function(',
|
||||
params.join(','),
|
||||
') {\n ',
|
||||
source,
|
||||
'}'
|
||||
'}',
|
||||
]);
|
||||
}
|
||||
},
|
||||
mergeSource: function(varDeclarations) {
|
||||
mergeSource: function (varDeclarations) {
|
||||
let isSimple = this.environment.isSimple,
|
||||
appendOnly = !this.forceBuffer,
|
||||
appendFirst,
|
||||
sourceSeen,
|
||||
bufferStart,
|
||||
bufferEnd;
|
||||
this.source.each(line => {
|
||||
this.source.each((line) => {
|
||||
if (line.appendToBuffer) {
|
||||
if (bufferStart) {
|
||||
line.prepend(' + ');
|
||||
@@ -329,7 +333,7 @@ JavaScriptCompiler.prototype = {
|
||||
return this.source.merge();
|
||||
},
|
||||
|
||||
lookupPropertyFunctionVarDeclaration: function() {
|
||||
lookupPropertyFunctionVarDeclaration: function () {
|
||||
return `
|
||||
lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
@@ -349,7 +353,7 @@ JavaScriptCompiler.prototype = {
|
||||
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function(name) {
|
||||
blockValue: function (name) {
|
||||
let blockHelperMissing = this.aliasable(
|
||||
'container.hooks.blockHelperMissing'
|
||||
),
|
||||
@@ -368,7 +372,7 @@ JavaScriptCompiler.prototype = {
|
||||
// Compiler value, before: lastHelper=value of last found helper, if any
|
||||
// On stack, after, if no lastHelper: same as [blockValue]
|
||||
// On stack, after, if lastHelper: value
|
||||
ambiguousBlockValue: function() {
|
||||
ambiguousBlockValue: function () {
|
||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||
let blockHelperMissing = this.aliasable(
|
||||
'container.hooks.blockHelperMissing'
|
||||
@@ -388,7 +392,7 @@ JavaScriptCompiler.prototype = {
|
||||
current,
|
||||
' = ',
|
||||
this.source.functionCall(blockHelperMissing, 'call', params),
|
||||
'}'
|
||||
'}',
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -398,7 +402,7 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Appends the string value of `content` to the current buffer
|
||||
appendContent: function(content) {
|
||||
appendContent: function (content) {
|
||||
if (this.pendingContent) {
|
||||
content = this.pendingContent + content;
|
||||
} else {
|
||||
@@ -417,9 +421,9 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// If `value` is truthy, or 0, it is coerced into a string and appended
|
||||
// Otherwise, the empty string is appended
|
||||
append: function() {
|
||||
append: function () {
|
||||
if (this.isInline()) {
|
||||
this.replaceStack(current => [' != null ? ', current, ' : ""']);
|
||||
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
|
||||
|
||||
this.pushSource(this.appendToBuffer(this.popStack()));
|
||||
} else {
|
||||
@@ -429,13 +433,13 @@ JavaScriptCompiler.prototype = {
|
||||
local,
|
||||
' != null) { ',
|
||||
this.appendToBuffer(local, undefined, true),
|
||||
' }'
|
||||
' }',
|
||||
]);
|
||||
if (this.environment.isSimple) {
|
||||
this.pushSource([
|
||||
'else { ',
|
||||
this.appendToBuffer("''", undefined, true),
|
||||
' }'
|
||||
' }',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -447,13 +451,13 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
appendEscaped: function () {
|
||||
this.pushSource(
|
||||
this.appendToBuffer([
|
||||
this.aliasable('container.escapeExpression'),
|
||||
'(',
|
||||
this.popStack(),
|
||||
')'
|
||||
')',
|
||||
])
|
||||
);
|
||||
},
|
||||
@@ -465,7 +469,7 @@ JavaScriptCompiler.prototype = {
|
||||
// Compiler value, after: lastContext=depth
|
||||
//
|
||||
// Set the value of the `lastContext` compiler value to the depth
|
||||
getContext: function(depth) {
|
||||
getContext: function (depth) {
|
||||
this.lastContext = depth;
|
||||
},
|
||||
|
||||
@@ -475,7 +479,7 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: currentContext, ...
|
||||
//
|
||||
// Pushes the value of the current context onto the stack.
|
||||
pushContext: function() {
|
||||
pushContext: function () {
|
||||
this.pushStackLiteral(this.contextName(this.lastContext));
|
||||
},
|
||||
|
||||
@@ -486,7 +490,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(parts, falsy, strict, scoped) {
|
||||
lookupOnContext: function (parts, falsy, strict, scoped) {
|
||||
let i = 0;
|
||||
|
||||
if (!scoped && this.options.compat && !this.lastContext) {
|
||||
@@ -507,7 +511,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Looks up the value of `parts` on the given block param and pushes
|
||||
// it onto the stack.
|
||||
lookupBlockParam: function(blockParamId, parts) {
|
||||
lookupBlockParam: function (blockParamId, parts) {
|
||||
this.useBlockParams = true;
|
||||
|
||||
this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
|
||||
@@ -520,7 +524,7 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: data, ...
|
||||
//
|
||||
// Push the data lookup operator
|
||||
lookupData: function(depth, parts, strict) {
|
||||
lookupData: function (depth, parts, strict) {
|
||||
if (!depth) {
|
||||
this.pushStackLiteral('data');
|
||||
} else {
|
||||
@@ -530,16 +534,24 @@ JavaScriptCompiler.prototype = {
|
||||
this.resolvePath('data', parts, 0, true, strict);
|
||||
},
|
||||
|
||||
resolvePath: function(type, parts, i, falsy, strict) {
|
||||
resolvePath: function (type, parts, startPartIndex, falsy, strict) {
|
||||
if (this.options.strict || this.options.assumeObjects) {
|
||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
||||
this.push(
|
||||
strictLookup(
|
||||
this.options.strict && strict,
|
||||
this,
|
||||
parts,
|
||||
startPartIndex,
|
||||
type
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let len = parts.length;
|
||||
for (; i < len; i++) {
|
||||
for (let i = startPartIndex; i < len; i++) {
|
||||
/* eslint-disable no-loop-func */
|
||||
this.replaceStack(current => {
|
||||
this.replaceStack((current) => {
|
||||
let lookup = this.nameLookup(current, parts[i], type);
|
||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||
// needs to have the special handling for these values.
|
||||
@@ -561,68 +573,30 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// If the `value` is a lambda, replace it on the stack by
|
||||
// the return value of the lambda
|
||||
resolvePossibleLambda: function() {
|
||||
resolvePossibleLambda: function () {
|
||||
this.push([
|
||||
this.aliasable('container.lambda'),
|
||||
'(',
|
||||
this.popStack(),
|
||||
', ',
|
||||
this.contextName(0),
|
||||
')'
|
||||
')',
|
||||
]);
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: string, currentContext, ...
|
||||
//
|
||||
// This opcode is designed for use in string mode, which
|
||||
// provides the string value of a parameter along with its
|
||||
// depth rather than resolving it immediately.
|
||||
pushStringParam: function(string, type) {
|
||||
this.pushContext();
|
||||
this.pushString(type);
|
||||
|
||||
// If it's a subexpression, the string result
|
||||
// will be pushed after this opcode.
|
||||
if (type !== 'SubExpression') {
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
emptyHash: function(omitEmpty) {
|
||||
if (this.trackIds) {
|
||||
this.push('{}'); // hashIds
|
||||
}
|
||||
if (this.stringParams) {
|
||||
this.push('{}'); // hashContexts
|
||||
this.push('{}'); // hashTypes
|
||||
}
|
||||
emptyHash: function (omitEmpty) {
|
||||
this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
|
||||
},
|
||||
pushHash: function() {
|
||||
pushHash: function () {
|
||||
if (this.hash) {
|
||||
this.hashes.push(this.hash);
|
||||
}
|
||||
this.hash = { values: {}, types: [], contexts: [], ids: [] };
|
||||
this.hash = { values: {} };
|
||||
},
|
||||
popHash: function() {
|
||||
popHash: function () {
|
||||
let hash = this.hash;
|
||||
this.hash = this.hashes.pop();
|
||||
|
||||
if (this.trackIds) {
|
||||
this.push(this.objectLiteral(hash.ids));
|
||||
}
|
||||
if (this.stringParams) {
|
||||
this.push(this.objectLiteral(hash.contexts));
|
||||
this.push(this.objectLiteral(hash.types));
|
||||
}
|
||||
|
||||
this.push(this.objectLiteral(hash.values));
|
||||
},
|
||||
|
||||
@@ -632,7 +606,7 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: quotedString(string), ...
|
||||
//
|
||||
// Push a quoted version of `string` onto the stack
|
||||
pushString: function(string) {
|
||||
pushString: function (string) {
|
||||
this.pushStackLiteral(this.quotedString(string));
|
||||
},
|
||||
|
||||
@@ -644,7 +618,7 @@ JavaScriptCompiler.prototype = {
|
||||
// Pushes a value onto the stack. This operation prevents
|
||||
// the compiler from creating a temporary variable to hold
|
||||
// it.
|
||||
pushLiteral: function(value) {
|
||||
pushLiteral: function (value) {
|
||||
this.pushStackLiteral(value);
|
||||
},
|
||||
|
||||
@@ -656,7 +630,7 @@ JavaScriptCompiler.prototype = {
|
||||
// Push a program expression onto the stack. This takes
|
||||
// a compile-time guid and converts it into a runtime-accessible
|
||||
// expression.
|
||||
pushProgram: function(guid) {
|
||||
pushProgram: function (guid) {
|
||||
if (guid != null) {
|
||||
this.pushStackLiteral(this.programExpression(guid));
|
||||
} else {
|
||||
@@ -681,9 +655,9 @@ JavaScriptCompiler.prototype = {
|
||||
'fn',
|
||||
'props',
|
||||
'container',
|
||||
options
|
||||
options,
|
||||
]),
|
||||
' || fn;'
|
||||
' || fn;',
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -696,7 +670,7 @@ JavaScriptCompiler.prototype = {
|
||||
// and pushes the helper's return value onto the stack.
|
||||
//
|
||||
// If the helper is not found, `helperMissing` is called.
|
||||
invokeHelper: function(paramSize, name, isSimple) {
|
||||
invokeHelper: function (paramSize, name, isSimple) {
|
||||
let nonHelper = this.popStack(),
|
||||
helper = this.setupHelper(paramSize, name);
|
||||
|
||||
@@ -717,7 +691,7 @@ JavaScriptCompiler.prototype = {
|
||||
let functionLookupCode = [
|
||||
'(',
|
||||
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
|
||||
')'
|
||||
')',
|
||||
];
|
||||
let functionCall = this.source.functionCall(
|
||||
functionLookupCode,
|
||||
@@ -727,7 +701,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.push(functionCall);
|
||||
},
|
||||
|
||||
itemsSeparatedBy: function(items, separator) {
|
||||
itemsSeparatedBy: function (items, separator) {
|
||||
let result = [];
|
||||
result.push(items[0]);
|
||||
for (let i = 1; i < items.length; i++) {
|
||||
@@ -742,7 +716,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// This operation is used when the helper is known to exist,
|
||||
// so a `helperMissing` fallback is not required.
|
||||
invokeKnownHelper: function(paramSize, name) {
|
||||
invokeKnownHelper: function (paramSize, name) {
|
||||
let helper = this.setupHelper(paramSize, name);
|
||||
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
|
||||
},
|
||||
@@ -759,7 +733,7 @@ JavaScriptCompiler.prototype = {
|
||||
// This operation emits more code than the other options,
|
||||
// and can be avoided by passing the `knownHelpers` and
|
||||
// `knownHelpersOnly` flags at compile-time.
|
||||
invokeAmbiguous: function(name, helperCall) {
|
||||
invokeAmbiguous: function (name, helperCall) {
|
||||
this.useRegister('helper');
|
||||
|
||||
let nonHelper = this.popStack();
|
||||
@@ -791,7 +765,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.aliasable('"function"'),
|
||||
' ? ',
|
||||
this.source.functionCall('helper', 'call', helper.callParams),
|
||||
' : helper))'
|
||||
' : helper))',
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -802,7 +776,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// This operation pops off a context, invokes a partial with that context,
|
||||
// and pushes the result of the invocation back.
|
||||
invokePartial: function(isDynamic, name, indent) {
|
||||
invokePartial: function (isDynamic, name, indent) {
|
||||
let params = [],
|
||||
options = this.setupParams(name, 1, params);
|
||||
|
||||
@@ -839,57 +813,15 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: ..., hash, ...
|
||||
//
|
||||
// Pops a value off the stack and assigns it to the current hash
|
||||
assignToHash: function(key) {
|
||||
let value = this.popStack(),
|
||||
context,
|
||||
type,
|
||||
id;
|
||||
|
||||
if (this.trackIds) {
|
||||
id = this.popStack();
|
||||
}
|
||||
if (this.stringParams) {
|
||||
type = this.popStack();
|
||||
context = this.popStack();
|
||||
}
|
||||
|
||||
let hash = this.hash;
|
||||
if (context) {
|
||||
hash.contexts[key] = context;
|
||||
}
|
||||
if (type) {
|
||||
hash.types[key] = type;
|
||||
}
|
||||
if (id) {
|
||||
hash.ids[key] = id;
|
||||
}
|
||||
hash.values[key] = value;
|
||||
},
|
||||
|
||||
pushId: function(type, name, child) {
|
||||
if (type === 'BlockParam') {
|
||||
this.pushStackLiteral(
|
||||
'blockParams[' +
|
||||
name[0] +
|
||||
'].path[' +
|
||||
name[1] +
|
||||
']' +
|
||||
(child ? ' + ' + JSON.stringify('.' + child) : '')
|
||||
);
|
||||
} else if (type === 'PathExpression') {
|
||||
this.pushString(name);
|
||||
} else if (type === 'SubExpression') {
|
||||
this.pushStackLiteral('true');
|
||||
} else {
|
||||
this.pushStackLiteral('null');
|
||||
}
|
||||
assignToHash: function (key) {
|
||||
this.hash.values[key] = this.popStack();
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
compileChildren: function(environment, options) {
|
||||
compileChildren: function (environment, options) {
|
||||
let children = environment.children,
|
||||
child,
|
||||
compiler;
|
||||
@@ -927,7 +859,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
},
|
||||
matchExistingProgram: function(child) {
|
||||
matchExistingProgram: function (child) {
|
||||
for (let i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
let environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
@@ -936,7 +868,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
programExpression: function (guid) {
|
||||
let child = this.environment.children[guid],
|
||||
programParams = [child.index, 'data', child.blockParams];
|
||||
|
||||
@@ -950,14 +882,14 @@ JavaScriptCompiler.prototype = {
|
||||
return 'container.program(' + programParams.join(', ') + ')';
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
useRegister: function (name) {
|
||||
if (!this.registers[name]) {
|
||||
this.registers[name] = true;
|
||||
this.registers.list.push(name);
|
||||
}
|
||||
},
|
||||
|
||||
push: function(expr) {
|
||||
push: function (expr) {
|
||||
if (!(expr instanceof Literal)) {
|
||||
expr = this.source.wrap(expr);
|
||||
}
|
||||
@@ -966,11 +898,11 @@ JavaScriptCompiler.prototype = {
|
||||
return expr;
|
||||
},
|
||||
|
||||
pushStackLiteral: function(item) {
|
||||
pushStackLiteral: function (item) {
|
||||
this.push(new Literal(item));
|
||||
},
|
||||
|
||||
pushSource: function(source) {
|
||||
pushSource: function (source) {
|
||||
if (this.pendingContent) {
|
||||
this.source.push(
|
||||
this.appendToBuffer(
|
||||
@@ -986,7 +918,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
replaceStack: function (callback) {
|
||||
let prefix = ['('],
|
||||
stack,
|
||||
createdStack,
|
||||
@@ -1025,17 +957,17 @@ JavaScriptCompiler.prototype = {
|
||||
this.push(prefix.concat(item, ')'));
|
||||
},
|
||||
|
||||
incrStack: function() {
|
||||
incrStack: function () {
|
||||
this.stackSlot++;
|
||||
if (this.stackSlot > this.stackVars.length) {
|
||||
this.stackVars.push('stack' + this.stackSlot);
|
||||
}
|
||||
return this.topStackName();
|
||||
},
|
||||
topStackName: function() {
|
||||
topStackName: function () {
|
||||
return 'stack' + this.stackSlot;
|
||||
},
|
||||
flushInline: function() {
|
||||
flushInline: function () {
|
||||
let inlineStack = this.inlineStack;
|
||||
this.inlineStack = [];
|
||||
for (let i = 0, len = inlineStack.length; i < len; i++) {
|
||||
@@ -1050,11 +982,11 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
},
|
||||
isInline: function() {
|
||||
isInline: function () {
|
||||
return this.inlineStack.length;
|
||||
},
|
||||
|
||||
popStack: function(wrapped) {
|
||||
popStack: function (wrapped) {
|
||||
let inline = this.isInline(),
|
||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||
|
||||
@@ -1072,7 +1004,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
topStack: function () {
|
||||
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
@@ -1084,7 +1016,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
contextName: function(context) {
|
||||
contextName: function (context) {
|
||||
if (this.useDepths && context) {
|
||||
return 'depths[' + context + ']';
|
||||
} else {
|
||||
@@ -1092,15 +1024,15 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
quotedString: function (str) {
|
||||
return this.source.quotedString(str);
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
objectLiteral: function (obj) {
|
||||
return this.source.objectLiteral(obj);
|
||||
},
|
||||
|
||||
aliasable: function(name) {
|
||||
aliasable: function (name) {
|
||||
let ret = this.aliases[name];
|
||||
if (ret) {
|
||||
ret.referenceCount++;
|
||||
@@ -1114,7 +1046,7 @@ JavaScriptCompiler.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name, blockHelper) {
|
||||
setupHelper: function (paramSize, name, blockHelper) {
|
||||
let params = [],
|
||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
||||
@@ -1128,15 +1060,12 @@ JavaScriptCompiler.prototype = {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: [callContext].concat(params)
|
||||
callParams: [callContext].concat(params),
|
||||
};
|
||||
},
|
||||
|
||||
setupParams: function(helper, paramSize, params) {
|
||||
setupParams: function (helper, paramSize, params) {
|
||||
let options = {},
|
||||
contexts = [],
|
||||
types = [],
|
||||
ids = [],
|
||||
objectArgs = !params,
|
||||
param;
|
||||
|
||||
@@ -1147,14 +1076,6 @@ JavaScriptCompiler.prototype = {
|
||||
options.name = this.quotedString(helper);
|
||||
options.hash = this.popStack();
|
||||
|
||||
if (this.trackIds) {
|
||||
options.hashIds = this.popStack();
|
||||
}
|
||||
if (this.stringParams) {
|
||||
options.hashTypes = this.popStack();
|
||||
options.hashContexts = this.popStack();
|
||||
}
|
||||
|
||||
let inverse = this.popStack(),
|
||||
program = this.popStack();
|
||||
|
||||
@@ -1171,28 +1092,12 @@ JavaScriptCompiler.prototype = {
|
||||
while (i--) {
|
||||
param = this.popStack();
|
||||
params[i] = param;
|
||||
|
||||
if (this.trackIds) {
|
||||
ids[i] = this.popStack();
|
||||
}
|
||||
if (this.stringParams) {
|
||||
types[i] = this.popStack();
|
||||
contexts[i] = this.popStack();
|
||||
}
|
||||
}
|
||||
|
||||
if (objectArgs) {
|
||||
options.args = this.source.generateArray(params);
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = this.source.generateArray(ids);
|
||||
}
|
||||
if (this.stringParams) {
|
||||
options.types = this.source.generateArray(types);
|
||||
options.contexts = this.source.generateArray(contexts);
|
||||
}
|
||||
|
||||
if (this.options.data) {
|
||||
options.data = 'data';
|
||||
}
|
||||
@@ -1202,7 +1107,7 @@ JavaScriptCompiler.prototype = {
|
||||
return options;
|
||||
},
|
||||
|
||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
||||
setupHelperArgs: function (helper, paramSize, params, useRegister) {
|
||||
let options = this.setupParams(helper, paramSize, params);
|
||||
options.loc = JSON.stringify(this.source.currentLocation);
|
||||
options = this.objectLiteral(options);
|
||||
@@ -1216,10 +1121,10 @@ JavaScriptCompiler.prototype = {
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
const reservedWords = (
|
||||
'break else new var' +
|
||||
' case finally return void' +
|
||||
@@ -1249,22 +1154,21 @@ JavaScriptCompiler.prototype = {
|
||||
/**
|
||||
* @deprecated May be removed in the next major version
|
||||
*/
|
||||
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
||||
JavaScriptCompiler.isValidJavaScriptVariableName = function (name) {
|
||||
return (
|
||||
!JavaScriptCompiler.RESERVED_WORDS[name] &&
|
||||
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
|
||||
);
|
||||
};
|
||||
|
||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
||||
function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
||||
let stack = compiler.popStack(),
|
||||
i = 0,
|
||||
len = parts.length;
|
||||
if (requireTerminal) {
|
||||
len--;
|
||||
}
|
||||
|
||||
for (; i < len; i++) {
|
||||
for (let i = startPartIndex; i < len; i++) {
|
||||
stack = compiler.nameLookup(stack, parts[i], type);
|
||||
}
|
||||
|
||||
@@ -1274,10 +1178,10 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
||||
'(',
|
||||
stack,
|
||||
', ',
|
||||
compiler.quotedString(parts[i]),
|
||||
compiler.quotedString(parts[len]),
|
||||
', ',
|
||||
JSON.stringify(compiler.source.currentLocation),
|
||||
' )'
|
||||
' )',
|
||||
];
|
||||
} else {
|
||||
return stack;
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
/* eslint-disable new-cap */
|
||||
import Visitor from './visitor';
|
||||
|
||||
export function print(ast) {
|
||||
return new PrintVisitor().accept(ast);
|
||||
}
|
||||
|
||||
export function PrintVisitor() {
|
||||
this.padding = 0;
|
||||
}
|
||||
|
||||
PrintVisitor.prototype = new Visitor();
|
||||
|
||||
PrintVisitor.prototype.pad = function(string) {
|
||||
let out = '';
|
||||
|
||||
for (let i = 0, l = this.padding; i < l; i++) {
|
||||
out += ' ';
|
||||
}
|
||||
|
||||
out += string + '\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.Program = function(program) {
|
||||
let out = '',
|
||||
body = program.body,
|
||||
i,
|
||||
l;
|
||||
|
||||
if (program.blockParams) {
|
||||
let blockParams = 'BLOCK PARAMS: [';
|
||||
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
||||
blockParams += ' ' + program.blockParams[i];
|
||||
}
|
||||
blockParams += ' ]';
|
||||
out += this.pad(blockParams);
|
||||
}
|
||||
|
||||
for (i = 0, l = body.length; i < l; i++) {
|
||||
out += this.accept(body[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.Decorator = function(mustache) {
|
||||
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
|
||||
block
|
||||
) {
|
||||
let out = '';
|
||||
|
||||
out += this.pad(
|
||||
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
|
||||
);
|
||||
this.padding++;
|
||||
out += this.pad(this.SubExpression(block));
|
||||
if (block.program) {
|
||||
out += this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
out += this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) {
|
||||
this.padding++;
|
||||
}
|
||||
out += this.pad('{{^}}');
|
||||
this.padding++;
|
||||
out += this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) {
|
||||
this.padding--;
|
||||
}
|
||||
}
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||
let content = 'PARTIAL:' + partial.name.original;
|
||||
if (partial.params[0]) {
|
||||
content += ' ' + this.accept(partial.params[0]);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += ' ' + this.accept(partial.hash);
|
||||
}
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
|
||||
let content = 'PARTIAL BLOCK:' + partial.name.original;
|
||||
if (partial.params[0]) {
|
||||
content += ' ' + this.accept(partial.params[0]);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += ' ' + this.accept(partial.hash);
|
||||
}
|
||||
|
||||
content += ' ' + this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
content += this.accept(partial.program);
|
||||
this.padding--;
|
||||
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.ContentStatement = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.value + "' ]");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.CommentStatement = function(comment) {
|
||||
return this.pad("{{! '" + comment.value + "' }}");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
||||
let params = sexpr.params,
|
||||
paramStrings = [],
|
||||
hash;
|
||||
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
paramStrings.push(this.accept(params[i]));
|
||||
}
|
||||
|
||||
params = '[' + paramStrings.join(', ') + ']';
|
||||
|
||||
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
|
||||
|
||||
return this.accept(sexpr.path) + ' ' + params + hash;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.PathExpression = function(id) {
|
||||
let path = id.parts.join('/');
|
||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||
return '"' + string.value + '"';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.NumberLiteral = function(number) {
|
||||
return 'NUMBER{' + number.value + '}';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BooleanLiteral = function(bool) {
|
||||
return 'BOOLEAN{' + bool.value + '}';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.UndefinedLiteral = function() {
|
||||
return 'UNDEFINED';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.NullLiteral = function() {
|
||||
return 'NULL';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.Hash = function(hash) {
|
||||
let pairs = hash.pairs,
|
||||
joinedPairs = [];
|
||||
|
||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||
joinedPairs.push(this.accept(pairs[i]));
|
||||
}
|
||||
|
||||
return 'HASH{' + joinedPairs.join(', ') + '}';
|
||||
};
|
||||
PrintVisitor.prototype.HashPair = function(pair) {
|
||||
return pair.key + '=' + this.accept(pair.value);
|
||||
};
|
||||
/* eslint-enable new-cap */
|
||||
@@ -1,136 +0,0 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
function Visitor() {
|
||||
this.parents = [];
|
||||
}
|
||||
|
||||
Visitor.prototype = {
|
||||
constructor: Visitor,
|
||||
mutating: false,
|
||||
|
||||
// Visits a given value. If mutating, will replace the value if necessary.
|
||||
acceptKey: function(node, name) {
|
||||
let value = this.accept(node[name]);
|
||||
if (this.mutating) {
|
||||
// Hacky sanity check: This may have a few false positives for type for the helper
|
||||
// methods but will generally do the right thing without a lot of overhead.
|
||||
if (value && !Visitor.prototype[value.type]) {
|
||||
throw new Exception(
|
||||
'Unexpected node type "' +
|
||||
value.type +
|
||||
'" found when accepting ' +
|
||||
name +
|
||||
' on ' +
|
||||
node.type
|
||||
);
|
||||
}
|
||||
node[name] = value;
|
||||
}
|
||||
},
|
||||
|
||||
// Performs an accept operation with added sanity check to ensure
|
||||
// required keys are not removed.
|
||||
acceptRequired: function(node, name) {
|
||||
this.acceptKey(node, name);
|
||||
|
||||
if (!node[name]) {
|
||||
throw new Exception(node.type + ' requires ' + name);
|
||||
}
|
||||
},
|
||||
|
||||
// Traverses a given array. If mutating, empty respnses will be removed
|
||||
// for child elements.
|
||||
acceptArray: function(array) {
|
||||
for (let i = 0, l = array.length; i < l; i++) {
|
||||
this.acceptKey(array, i);
|
||||
|
||||
if (!array[i]) {
|
||||
array.splice(i, 1);
|
||||
i--;
|
||||
l--;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
accept: function(object) {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[object.type]) {
|
||||
throw new Exception('Unknown type: ' + object.type, object);
|
||||
}
|
||||
|
||||
if (this.current) {
|
||||
this.parents.unshift(this.current);
|
||||
}
|
||||
this.current = object;
|
||||
|
||||
let ret = this[object.type](object);
|
||||
|
||||
this.current = this.parents.shift();
|
||||
|
||||
if (!this.mutating || ret) {
|
||||
return ret;
|
||||
} else if (ret !== false) {
|
||||
return object;
|
||||
}
|
||||
},
|
||||
|
||||
Program: function(program) {
|
||||
this.acceptArray(program.body);
|
||||
},
|
||||
|
||||
MustacheStatement: visitSubExpression,
|
||||
Decorator: visitSubExpression,
|
||||
|
||||
BlockStatement: visitBlock,
|
||||
DecoratorBlock: visitBlock,
|
||||
|
||||
PartialStatement: visitPartial,
|
||||
PartialBlockStatement: function(partial) {
|
||||
visitPartial.call(this, partial);
|
||||
|
||||
this.acceptKey(partial, 'program');
|
||||
},
|
||||
|
||||
ContentStatement: function(/* content */) {},
|
||||
CommentStatement: function(/* comment */) {},
|
||||
|
||||
SubExpression: visitSubExpression,
|
||||
|
||||
PathExpression: function(/* path */) {},
|
||||
|
||||
StringLiteral: function(/* string */) {},
|
||||
NumberLiteral: function(/* number */) {},
|
||||
BooleanLiteral: function(/* bool */) {},
|
||||
UndefinedLiteral: function(/* literal */) {},
|
||||
NullLiteral: function(/* literal */) {},
|
||||
|
||||
Hash: function(hash) {
|
||||
this.acceptArray(hash.pairs);
|
||||
},
|
||||
HashPair: function(pair) {
|
||||
this.acceptRequired(pair, 'value');
|
||||
}
|
||||
};
|
||||
|
||||
function visitSubExpression(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
}
|
||||
function visitBlock(block) {
|
||||
visitSubExpression.call(this, block);
|
||||
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
}
|
||||
function visitPartial(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
}
|
||||
|
||||
export default Visitor;
|
||||
@@ -1,234 +0,0 @@
|
||||
import Visitor from './visitor';
|
||||
|
||||
function WhitespaceControl(options = {}) {
|
||||
this.options = options;
|
||||
}
|
||||
WhitespaceControl.prototype = new Visitor();
|
||||
|
||||
WhitespaceControl.prototype.Program = function(program) {
|
||||
const doStandalone = !this.options.ignoreStandalone;
|
||||
|
||||
let isRoot = !this.isRootSeen;
|
||||
this.isRootSeen = true;
|
||||
|
||||
let body = program.body;
|
||||
for (let i = 0, l = body.length; i < l; i++) {
|
||||
let current = body[i],
|
||||
strip = this.accept(current);
|
||||
|
||||
if (!strip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone =
|
||||
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
|
||||
if (strip.close) {
|
||||
omitRight(body, i, true);
|
||||
}
|
||||
if (strip.open) {
|
||||
omitLeft(body, i, true);
|
||||
}
|
||||
|
||||
if (doStandalone && inlineStandalone) {
|
||||
omitRight(body, i);
|
||||
|
||||
if (omitLeft(body, i)) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if (current.type === 'PartialStatement') {
|
||||
// Pull out the whitespace from the final line
|
||||
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doStandalone && openStandalone) {
|
||||
omitRight((current.program || current.inverse).body);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i);
|
||||
}
|
||||
if (doStandalone && closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(body, i);
|
||||
|
||||
omitLeft((current.inverse || current.program).body);
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
|
||||
block
|
||||
) {
|
||||
this.accept(block.program);
|
||||
this.accept(block.inverse);
|
||||
|
||||
// Find the inverse program that is involed with whitespace stripping.
|
||||
let program = block.program || block.inverse,
|
||||
inverse = block.program && block.inverse,
|
||||
firstInverse = inverse,
|
||||
lastInverse = inverse;
|
||||
|
||||
if (inverse && inverse.chained) {
|
||||
firstInverse = inverse.body[0].program;
|
||||
|
||||
// Walk the inverse chain to find the last inverse that is actually in the chain.
|
||||
while (lastInverse.chained) {
|
||||
lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
|
||||
}
|
||||
}
|
||||
|
||||
let strip = {
|
||||
open: block.openStrip.open,
|
||||
close: block.closeStrip.close,
|
||||
|
||||
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
||||
// so our parent can determine if we actually are standalone
|
||||
openStandalone: isNextWhitespace(program.body),
|
||||
closeStandalone: isPrevWhitespace((firstInverse || program).body)
|
||||
};
|
||||
|
||||
if (block.openStrip.close) {
|
||||
omitRight(program.body, null, true);
|
||||
}
|
||||
|
||||
if (inverse) {
|
||||
let inverseStrip = block.inverseStrip;
|
||||
|
||||
if (inverseStrip.open) {
|
||||
omitLeft(program.body, null, true);
|
||||
}
|
||||
|
||||
if (inverseStrip.close) {
|
||||
omitRight(firstInverse.body, null, true);
|
||||
}
|
||||
if (block.closeStrip.open) {
|
||||
omitLeft(lastInverse.body, null, true);
|
||||
}
|
||||
|
||||
// Find standalone else statments
|
||||
if (
|
||||
!this.options.ignoreStandalone &&
|
||||
isPrevWhitespace(program.body) &&
|
||||
isNextWhitespace(firstInverse.body)
|
||||
) {
|
||||
omitLeft(program.body);
|
||||
omitRight(firstInverse.body);
|
||||
}
|
||||
} else if (block.closeStrip.open) {
|
||||
omitLeft(program.body, null, true);
|
||||
}
|
||||
|
||||
return strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
|
||||
mustache
|
||||
) {
|
||||
return mustache.strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
|
||||
node
|
||||
) {
|
||||
/* istanbul ignore next */
|
||||
let strip = node.strip || {};
|
||||
return {
|
||||
inlineStandalone: true,
|
||||
open: strip.open,
|
||||
close: strip.close
|
||||
};
|
||||
};
|
||||
|
||||
function isPrevWhitespace(body, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = body.length;
|
||||
}
|
||||
|
||||
// Nodes that end with newlines are considered whitespace (but are special
|
||||
// cased for strip operations)
|
||||
let prev = body[i - 1],
|
||||
sibling = body[i - 2];
|
||||
if (!prev) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (prev.type === 'ContentStatement') {
|
||||
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
|
||||
prev.original
|
||||
);
|
||||
}
|
||||
}
|
||||
function isNextWhitespace(body, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = -1;
|
||||
}
|
||||
|
||||
let next = body[i + 1],
|
||||
sibling = body[i + 2];
|
||||
if (!next) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (next.type === 'ContentStatement') {
|
||||
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
|
||||
next.original
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Marks the node to the right of the position as omitted.
|
||||
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined, then the first child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitRight(body, i, multiple) {
|
||||
let current = body[i == null ? 0 : i + 1];
|
||||
if (
|
||||
!current ||
|
||||
current.type !== 'ContentStatement' ||
|
||||
(!multiple && current.rightStripped)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let original = current.value;
|
||||
current.value = current.value.replace(
|
||||
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
|
||||
''
|
||||
);
|
||||
current.rightStripped = current.value !== original;
|
||||
}
|
||||
|
||||
// Marks the node to the left of the position as omitted.
|
||||
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined then the last child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitLeft(body, i, multiple) {
|
||||
let current = body[i == null ? body.length - 1 : i - 1];
|
||||
if (
|
||||
!current ||
|
||||
current.type !== 'ContentStatement' ||
|
||||
(!multiple && current.leftStripped)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
||||
let original = current.value;
|
||||
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
|
||||
current.leftStripped = current.value !== original;
|
||||
return current.leftStripped;
|
||||
}
|
||||
|
||||
export default WhitespaceControl;
|
||||
@@ -1,22 +1,25 @@
|
||||
import { extend } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function(context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
export default function (instance) {
|
||||
instance.registerDecorator(
|
||||
'inline',
|
||||
function (fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function (context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
const errorProps = [
|
||||
'description',
|
||||
'fileName',
|
||||
'lineNumber',
|
||||
'endLineNumber',
|
||||
'message',
|
||||
'name',
|
||||
'number',
|
||||
'stack'
|
||||
];
|
||||
|
||||
function Exception(message, node) {
|
||||
let loc = node && node.loc,
|
||||
line,
|
||||
endLineNumber,
|
||||
column,
|
||||
endColumn;
|
||||
|
||||
if (loc) {
|
||||
line = loc.start.line;
|
||||
endLineNumber = loc.end.line;
|
||||
column = loc.start.column;
|
||||
endColumn = loc.end.column;
|
||||
|
||||
message += ' - ' + line + ':' + column;
|
||||
}
|
||||
|
||||
let tmp = Error.prototype.constructor.call(this, message);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (let idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, Exception);
|
||||
}
|
||||
|
||||
try {
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
this.endLineNumber = endLineNumber;
|
||||
|
||||
// Work around issue under safari where we can't directly set the column value
|
||||
/* istanbul ignore next */
|
||||
if (Object.defineProperty) {
|
||||
Object.defineProperty(this, 'column', {
|
||||
value: column,
|
||||
enumerable: true
|
||||
});
|
||||
Object.defineProperty(this, 'endColumn', {
|
||||
value: endColumn,
|
||||
enumerable: true
|
||||
});
|
||||
} else {
|
||||
this.column = column;
|
||||
this.endColumn = endColumn;
|
||||
}
|
||||
}
|
||||
} catch (nop) {
|
||||
/* Ignore if the browser is very particular */
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
export default Exception;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { appendContextPath, createFrame, isArray } from '../utils';
|
||||
import { isArray } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('blockHelperMissing', function (context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
@@ -11,24 +11,11 @@ export default function(instance) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(
|
||||
options.data.contextPath,
|
||||
options.name
|
||||
);
|
||||
options = { data: data };
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
appendContextPath,
|
||||
blockParams,
|
||||
createFrame,
|
||||
isArray,
|
||||
isFunction
|
||||
} from '../utils';
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, isArray, isFunction } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('each', function (context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
@@ -17,13 +11,7 @@ export default function(instance) {
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath =
|
||||
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
data;
|
||||
|
||||
if (isFunction(context)) {
|
||||
context = context.call(this);
|
||||
@@ -39,20 +27,13 @@ export default function(instance) {
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret =
|
||||
ret +
|
||||
fn(context[field], {
|
||||
data: data,
|
||||
blockParams: blockParams(
|
||||
[context[field], field],
|
||||
[contextPath + field, null]
|
||||
)
|
||||
blockParams: [context[field], field],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,9 +44,9 @@ export default function(instance) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
}
|
||||
} else if (global.Symbol && context[global.Symbol.iterator]) {
|
||||
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||
const newContext = [];
|
||||
const iterator = context[global.Symbol.iterator]();
|
||||
const iterator = context[Symbol.iterator]();
|
||||
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||
newContext.push(it.value);
|
||||
}
|
||||
@@ -76,10 +57,10 @@ export default function(instance) {
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
Object.keys(context).forEach(key => {
|
||||
Object.keys(context).forEach((key) => {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
// an intermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('helperMissing', function (/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} construct.
|
||||
return undefined;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('if', function (conditional, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#if requires exactly one argument');
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default function(instance) {
|
||||
}
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// The `includeZero` option may be set to treat the conditional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
@@ -20,14 +20,14 @@ export default function(instance) {
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
instance.registerHelper('unless', function (conditional, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#unless requires exactly one argument');
|
||||
}
|
||||
return instance.helpers['if'].call(this, conditional, {
|
||||
fn: options.inverse,
|
||||
inverse: options.fn,
|
||||
hash: options.hash
|
||||
hash: options.hash,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('log', function(/* message, options */) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('log', function (/* message, options */) {
|
||||
let args = [undefined],
|
||||
options = arguments[arguments.length - 1];
|
||||
for (let i = 0; i < arguments.length - 1; i++) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('lookup', function(obj, field, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('lookup', function (obj, field, options) {
|
||||
if (!obj) {
|
||||
// Note for 5.0: Change to "obj == null" in 5.0
|
||||
return obj;
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
appendContextPath,
|
||||
blockParams,
|
||||
createFrame,
|
||||
isEmpty,
|
||||
isFunction
|
||||
} from '../utils';
|
||||
import Exception from '../exception';
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
export default function (instance) {
|
||||
instance.registerHelper('with', function (context, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#with requires exactly one argument');
|
||||
}
|
||||
@@ -20,17 +14,10 @@ export default function(instance) {
|
||||
|
||||
if (!isEmpty(context)) {
|
||||
let data = options.data;
|
||||
if (options.data && options.ids) {
|
||||
data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(
|
||||
options.data.contextPath,
|
||||
options.ids[0]
|
||||
);
|
||||
}
|
||||
|
||||
return fn(context, {
|
||||
data: data,
|
||||
blockParams: blockParams([context], [data && data.contextPath])
|
||||
blockParams: [context],
|
||||
});
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createNewLookupObject } from './create-new-lookup-object';
|
||||
import * as logger from '../logger';
|
||||
import logger from '../logger';
|
||||
|
||||
const loggedProperties = Object.create(null);
|
||||
|
||||
@@ -20,15 +20,15 @@ export function createProtoAccessControl(runtimeOptions) {
|
||||
defaultPropertyWhiteList,
|
||||
runtimeOptions.allowedProtoProperties
|
||||
),
|
||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
|
||||
},
|
||||
methods: {
|
||||
whitelist: createNewLookupObject(
|
||||
defaultMethodWhiteList,
|
||||
runtimeOptions.allowedProtoMethods
|
||||
),
|
||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||
}
|
||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ function checkWhiteList(protoAccessControlForType, propertyName) {
|
||||
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||
return protoAccessControlForType.defaultValue;
|
||||
}
|
||||
logUnexpecedPropertyAccessOnce(propertyName);
|
||||
logUnexpectedPropertyAccessOnce(propertyName);
|
||||
return false;
|
||||
}
|
||||
|
||||
function logUnexpecedPropertyAccessOnce(propertyName) {
|
||||
function logUnexpectedPropertyAccessOnce(propertyName) {
|
||||
if (loggedProperties[propertyName] !== true) {
|
||||
loggedProperties[propertyName] = true;
|
||||
logger.log(
|
||||
@@ -64,7 +64,7 @@ function logUnexpecedPropertyAccessOnce(propertyName) {
|
||||
}
|
||||
|
||||
export function resetLoggedProperties() {
|
||||
Object.keys(loggedProperties).forEach(propertyName => {
|
||||
Object.keys(loggedProperties).forEach((propertyName) => {
|
||||
delete loggedProperties[propertyName];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export function wrapHelper(helper, transformOptionsFn) {
|
||||
if (typeof helper !== 'function') {
|
||||
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
|
||||
// This should not happen, but apparently it does in https://github.com/handlebars-lang/handlebars.js/issues/1639
|
||||
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
|
||||
return helper;
|
||||
}
|
||||
let wrapper = function(/* dynamic arguments */) {
|
||||
let wrapper = function (/* dynamic arguments */) {
|
||||
const options = arguments[arguments.length - 1];
|
||||
arguments[arguments.length - 1] = transformOptionsFn(options);
|
||||
return helper.apply(this, arguments);
|
||||
|
||||
@@ -5,7 +5,7 @@ let logger = {
|
||||
level: 'info',
|
||||
|
||||
// Maps a given level value to the `methodMap` indexes above.
|
||||
lookupLevel: function(level) {
|
||||
lookupLevel: function (level) {
|
||||
if (typeof level === 'string') {
|
||||
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||
if (levelMap >= 0) {
|
||||
@@ -19,7 +19,7 @@ let logger = {
|
||||
},
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, ...message) {
|
||||
log: function (level, ...message) {
|
||||
level = logger.lookupLevel(level);
|
||||
|
||||
if (
|
||||
@@ -33,7 +33,7 @@ let logger = {
|
||||
}
|
||||
console[method](...message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default logger;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
export default function(Handlebars) {
|
||||
export default function (Handlebars) {
|
||||
let $Handlebars = globalThis.Handlebars;
|
||||
|
||||
/* istanbul ignore next */
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
$Handlebars = root.Handlebars;
|
||||
/* istanbul ignore next */
|
||||
Handlebars.noConflict = function() {
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
Handlebars.noConflict = function () {
|
||||
if (globalThis.Handlebars === Handlebars) {
|
||||
globalThis.Handlebars = $Handlebars;
|
||||
}
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
+23
-45
@@ -1,16 +1,16 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
import {
|
||||
COMPILER_REVISION,
|
||||
createFrame,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES
|
||||
REVISION_CHANGES,
|
||||
} from './base';
|
||||
import { moveHelperToHooks } from './helpers';
|
||||
import { wrapHelper } from './internal/wrapHelper';
|
||||
import {
|
||||
createProtoAccessControl,
|
||||
resultIsAllowed
|
||||
resultIsAllowed,
|
||||
} from './internal/proto-access';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
@@ -68,15 +68,12 @@ export function template(templateSpec, env) {
|
||||
function invokePartialWrapper(partial, context, options) {
|
||||
if (options.hash) {
|
||||
context = Utils.extend({}, context, options.hash);
|
||||
if (options.ids) {
|
||||
options.ids[0] = true;
|
||||
}
|
||||
}
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
|
||||
let extendedOptions = Utils.extend({}, options, {
|
||||
hooks: this.hooks,
|
||||
protoAccessControl: this.protoAccessControl
|
||||
protoAccessControl: this.protoAccessControl,
|
||||
});
|
||||
|
||||
let result = env.VM.invokePartial.call(
|
||||
@@ -118,15 +115,15 @@ export function template(templateSpec, env) {
|
||||
|
||||
// Just add water
|
||||
let container = {
|
||||
strict: function(obj, name, loc) {
|
||||
strict: function (obj, name, loc) {
|
||||
if (!obj || !(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||
loc: loc
|
||||
loc: loc,
|
||||
});
|
||||
}
|
||||
return obj[name];
|
||||
return container.lookupProperty(obj, name);
|
||||
},
|
||||
lookupProperty: function(parent, propertyName) {
|
||||
lookupProperty: function (parent, propertyName) {
|
||||
let result = parent[propertyName];
|
||||
if (result == null) {
|
||||
return result;
|
||||
@@ -140,7 +137,7 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
lookup: function(depths, name) {
|
||||
lookup: function (depths, name) {
|
||||
const len = depths.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||
@@ -149,21 +146,21 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
lambda: function (current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
fn: function (i) {
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||
program: function (i, data, declaredBlockParams, blockParams, depths) {
|
||||
let programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if (data || depths || blockParams || declaredBlockParams) {
|
||||
@@ -182,13 +179,13 @@ export function template(templateSpec, env) {
|
||||
return programWrapper;
|
||||
},
|
||||
|
||||
data: function(value, depth) {
|
||||
data: function (value, depth) {
|
||||
while (value && depth--) {
|
||||
value = value._parent;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
mergeIfNeeded: function(param, common) {
|
||||
mergeIfNeeded: function (param, common) {
|
||||
let obj = param || common;
|
||||
|
||||
if (param && common && param !== common) {
|
||||
@@ -201,13 +198,13 @@ export function template(templateSpec, env) {
|
||||
nullContext: Object.seal({}),
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
compilerInfo: templateSpec.compiler,
|
||||
};
|
||||
|
||||
function ret(context, options = {}) {
|
||||
let data = options.data;
|
||||
|
||||
ret._setup(options);
|
||||
_setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
@@ -252,7 +249,7 @@ export function template(templateSpec, env) {
|
||||
|
||||
ret.isTop = true;
|
||||
|
||||
ret._setup = function(options) {
|
||||
function _setup(options) {
|
||||
if (!options.partial) {
|
||||
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
||||
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
||||
@@ -288,26 +285,8 @@ export function template(templateSpec, env) {
|
||||
container.decorators = options.decorators;
|
||||
container.hooks = options.hooks;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (templateSpec.useBlockParams && !blockParams) {
|
||||
throw new Exception('must pass block params');
|
||||
}
|
||||
if (templateSpec.useDepths && !depths) {
|
||||
throw new Exception('must pass parent depths');
|
||||
}
|
||||
|
||||
return wrapProgram(
|
||||
container,
|
||||
i,
|
||||
templateSpec[i],
|
||||
data,
|
||||
0,
|
||||
blockParams,
|
||||
depths
|
||||
);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -371,9 +350,6 @@ export function invokePartial(partial, context, options) {
|
||||
// Use the current closure context to save the partial-block if this partial
|
||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
||||
options.partial = true;
|
||||
if (options.ids) {
|
||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||
}
|
||||
|
||||
let partialBlock;
|
||||
if (options.fn && options.fn !== noop) {
|
||||
@@ -400,7 +376,9 @@ export function invokePartial(partial, context, options) {
|
||||
}
|
||||
|
||||
if (partial === undefined) {
|
||||
throw new Exception('The partial ' + options.name + ' could not be found');
|
||||
throw new Exception(
|
||||
'The partial "' + options.name + '" could not be found'
|
||||
);
|
||||
} else if (partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
}
|
||||
@@ -436,7 +414,7 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||
}
|
||||
|
||||
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||
Object.keys(mergedHelpers).forEach(helperName => {
|
||||
Object.keys(mergedHelpers).forEach((helperName) => {
|
||||
let helper = mergedHelpers[helperName];
|
||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||
});
|
||||
@@ -444,7 +422,7 @@ function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||
|
||||
function passLookupPropertyOption(helper, container) {
|
||||
const lookupProperty = container.lookupProperty;
|
||||
return wrapHelper(helper, options => {
|
||||
return wrapHelper(helper, (options) => {
|
||||
return Utils.extend({ lookupProperty }, options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
|
||||
return '' + this.string;
|
||||
};
|
||||
|
||||
|
||||
+4
-26
@@ -5,7 +5,7 @@ const escape = {
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
'=': '=',
|
||||
};
|
||||
|
||||
const badChars = /[&<>"'`=]/g,
|
||||
@@ -30,28 +30,15 @@ export function extend(obj /* , ...source */) {
|
||||
export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/* eslint-disable func-style */
|
||||
let isFunction = function(value) {
|
||||
// https://github.com/lodash/lodash/blob/4.17.21/LICENSE
|
||||
export function isFunction(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return (
|
||||
typeof value === 'function' &&
|
||||
toString.call(value) === '[object Function]'
|
||||
);
|
||||
};
|
||||
}
|
||||
export { isFunction };
|
||||
/* eslint-enable func-style */
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray =
|
||||
Array.isArray ||
|
||||
function(value) {
|
||||
function (value) {
|
||||
return value && typeof value === 'object'
|
||||
? toString.call(value) === '[object Array]'
|
||||
: false;
|
||||
@@ -105,12 +92,3 @@ export function createFrame(object) {
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function blockParams(params, ids) {
|
||||
params.path = ids;
|
||||
return params;
|
||||
}
|
||||
|
||||
export function appendContextPath(contextPath, id) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,14 +1,15 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-var */
|
||||
|
||||
// var local = handlebars.create();
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||
|
||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||
handlebars.print = printer.print;
|
||||
var parser = require('@handlebars/parser');
|
||||
handlebars.PrintVisitor = parser.PrintVisitor;
|
||||
handlebars.print = parser.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
|
||||
+29
-23
@@ -1,3 +1,4 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'neo-async';
|
||||
import fs from 'fs';
|
||||
@@ -5,12 +6,12 @@ import * as Handlebars from './handlebars';
|
||||
import { basename } from 'path';
|
||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||
|
||||
module.exports.loadTemplates = function(opts, callback) {
|
||||
loadStrings(opts, function(err, strings) {
|
||||
module.exports.loadTemplates = function (opts, callback) {
|
||||
loadStrings(opts, function (err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
loadFiles(opts, function(err, files) {
|
||||
loadFiles(opts, function (err, files) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
@@ -36,7 +37,7 @@ function loadStrings(opts, callback) {
|
||||
|
||||
Async.map(
|
||||
strings,
|
||||
function(string, callback) {
|
||||
function (string, callback) {
|
||||
if (string !== '-') {
|
||||
callback(undefined, string);
|
||||
} else {
|
||||
@@ -44,19 +45,19 @@ function loadStrings(opts, callback) {
|
||||
let buffer = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', function(chunk) {
|
||||
process.stdin.on('data', function (chunk) {
|
||||
buffer += chunk;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
process.stdin.on('end', function () {
|
||||
callback(undefined, buffer);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err, strings) {
|
||||
function (err, strings) {
|
||||
strings = strings.map((string, index) => ({
|
||||
name: names[index],
|
||||
path: names[index],
|
||||
source: string
|
||||
source: string,
|
||||
}));
|
||||
callback(err, strings);
|
||||
}
|
||||
@@ -67,20 +68,23 @@ function loadFiles(opts, callback) {
|
||||
// Build file extension pattern
|
||||
let extension = (opts.extension || 'handlebars').replace(
|
||||
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||
function(arg) {
|
||||
function (arg) {
|
||||
return '\\' + arg;
|
||||
}
|
||||
);
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let ret = [],
|
||||
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
|
||||
queue = (opts.files || []).map((template) => ({
|
||||
template,
|
||||
root: opts.root,
|
||||
}));
|
||||
Async.whilst(
|
||||
() => queue.length,
|
||||
function(callback) {
|
||||
function (callback) {
|
||||
let { template: path, root } = queue.shift();
|
||||
|
||||
fs.stat(path, function(err, stat) {
|
||||
fs.stat(path, function (err, stat) {
|
||||
if (err) {
|
||||
return callback(
|
||||
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||
@@ -90,12 +94,12 @@ function loadFiles(opts, callback) {
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdir(path, function(err, children) {
|
||||
fs.readdir(path, function (err, children) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
children.forEach(function(file) {
|
||||
children.forEach(function (file) {
|
||||
let childPath = path + '/' + file;
|
||||
|
||||
if (
|
||||
@@ -109,7 +113,7 @@ function loadFiles(opts, callback) {
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function(err, data) {
|
||||
fs.readFile(path, 'utf8', function (err, data) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@@ -131,7 +135,7 @@ function loadFiles(opts, callback) {
|
||||
ret.push({
|
||||
path: path,
|
||||
name: name,
|
||||
source: data
|
||||
source: data,
|
||||
});
|
||||
|
||||
callback();
|
||||
@@ -139,7 +143,7 @@ function loadFiles(opts, callback) {
|
||||
}
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
@@ -149,7 +153,7 @@ function loadFiles(opts, callback) {
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
module.exports.cli = function (opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
@@ -205,6 +209,8 @@ module.exports.cli = function(opts) {
|
||||
);
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else if (opts.esm) {
|
||||
output.add(`import handlebars from "${opts.esm}";`);
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
@@ -218,10 +224,10 @@ module.exports.cli = function(opts) {
|
||||
output.add('{};\n');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
opts.templates.forEach(function (template) {
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
knownHelpersOnly: opts.o,
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
@@ -258,7 +264,7 @@ module.exports.cli = function(opts) {
|
||||
template.name,
|
||||
"'] = template(",
|
||||
precompiled,
|
||||
');\n'
|
||||
');\n',
|
||||
]);
|
||||
}
|
||||
});
|
||||
@@ -334,7 +340,7 @@ function minify(output, sourceMapFile) {
|
||||
return require('uglify-js').minify(output.code, {
|
||||
sourceMap: {
|
||||
content: output.map,
|
||||
url: sourceMapFile
|
||||
}
|
||||
url: sourceMapFile,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,6 +4,6 @@ module.exports = {
|
||||
lines: 100,
|
||||
functions: 100,
|
||||
statements: 100,
|
||||
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
|
||||
reporter: 'html'
|
||||
exclude: ['**/spec/**'],
|
||||
reporter: 'html',
|
||||
};
|
||||
|
||||
Generated
+16848
-1883
File diff suppressed because it is too large
Load Diff
+43
-36
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"homepage": "https://handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
@@ -12,24 +11,26 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"readmeFilename": "README.markdown",
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"neo-async": "^2.6.0",
|
||||
"@handlebars/parser": "^2.1.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"yargs": "^15.3.1"
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"peerDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@knappi/grunt-saucelabs": "^9.0.2",
|
||||
"@definitelytyped/dtslint": "^0.0.100",
|
||||
"@playwright/test": "^1.36.1",
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
@@ -38,65 +39,66 @@
|
||||
"chai-diff": "^1.0.1",
|
||||
"concurrently": "^5.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"dtslint": "^0.5.5",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-config-prettier": "^6.7.0",
|
||||
"eslint-plugin-compat": "^3.3.0",
|
||||
"eslint-plugin-es5": "^1.4.1",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-prettier": "^8.9.0",
|
||||
"eslint-plugin-compat": "4.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"grunt": "^1.0.4",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-bg-shell": "^2.3.3",
|
||||
"grunt-cli": "^1",
|
||||
"grunt-contrib-clean": "^1",
|
||||
"grunt-contrib-concat": "^1",
|
||||
"grunt-contrib-connect": "^1",
|
||||
"grunt-contrib-connect": "^3.0.0",
|
||||
"grunt-contrib-copy": "^1",
|
||||
"grunt-contrib-requirejs": "^1",
|
||||
"grunt-contrib-uglify": "^1",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-shell": "^4.0.0",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"husky": "^3.1.0",
|
||||
"jison": "~0.3.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mocha": "^5",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"nyc": "^14.1.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier": "^3.0.0",
|
||||
"semver": "^5.0.1",
|
||||
"sinon": "^7.5.0",
|
||||
"typescript": "^3.4.3",
|
||||
"uglify-js": "^3.1.4",
|
||||
"underscore": "^1.5.1",
|
||||
"webpack": "^1.12.6",
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"browser": {
|
||||
".": "./dist/cjs/handlebars.js",
|
||||
"./runtime": "./dist/cjs/handlebars.runtime.js"
|
||||
},
|
||||
"browser": "./dist/cjs/handlebars.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
"handlebars": "bin/handlebars.js"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write '**/*.js' && eslint --fix .",
|
||||
"check-format": "prettier --check '**/*.js'",
|
||||
"lint": "eslint --max-warnings 0 .",
|
||||
"dtslint": "dtslint types",
|
||||
"test": "grunt",
|
||||
"extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",
|
||||
"integration-test": "grunt integration-tests",
|
||||
"build": "grunt build",
|
||||
"release": "grunt release",
|
||||
"publish:aws": "npm run test:tasks && grunt && grunt publish-to-aws",
|
||||
"format": "prettier --write '**/*.{js,css,json,md}' && eslint --fix .",
|
||||
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||
"lint:eslint": "eslint --max-warnings 0 .",
|
||||
"lint:prettier": "prettier --check '**/*.js'",
|
||||
"lint:types": "dtslint types",
|
||||
"test": "npm run test:mocha",
|
||||
"test:mocha": "grunt build && grunt test",
|
||||
"test:tasks": "mocha tasks/tests/",
|
||||
"test:browser": "playwright test --config tests/browser/playwright.config.js",
|
||||
"test:integration": "grunt integration-tests",
|
||||
"test:serve": "grunt connect:server:keepalive",
|
||||
"--- combined tasks ---": "",
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:dtslint npm:check-format npm:test"
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/amd"
|
||||
"lib": "dist/cjs"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
@@ -105,15 +107,20 @@
|
||||
"files": [
|
||||
"bin",
|
||||
"dist/*.js",
|
||||
"dist/amd/**/*.js",
|
||||
"dist/cjs/**/*.js",
|
||||
"lib",
|
||||
"print-script",
|
||||
"release-notes.md",
|
||||
"runtime.js",
|
||||
"types/*.d.ts",
|
||||
"runtime.d.ts"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead",
|
||||
"not IE 11",
|
||||
"maintained node versions"
|
||||
],
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true
|
||||
singleQuote: true,
|
||||
trailingComma: 'es5',
|
||||
};
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#! /usr/bin/env node
|
||||
/* eslint-disable no-console, no-var */
|
||||
// Util script for debugging source code generation issues
|
||||
|
||||
var script = process.argv[2].replace(/\\n/g, '\n'),
|
||||
verbose = process.argv[3] === '-v';
|
||||
|
||||
var Handlebars = require('./lib'),
|
||||
SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
|
||||
var template = Handlebars.precompile(script, {
|
||||
srcName: 'input.hbs',
|
||||
destName: 'output.js',
|
||||
|
||||
assumeObjects: true,
|
||||
compat: false,
|
||||
strict: true,
|
||||
trackIds: true,
|
||||
knownHelpersOnly: false
|
||||
});
|
||||
|
||||
if (!verbose) {
|
||||
console.log(template);
|
||||
} else {
|
||||
var consumer = new SourceMapConsumer(template.map),
|
||||
lines = template.code.split('\n'),
|
||||
srcLines = script.split('\n');
|
||||
|
||||
console.log();
|
||||
console.log('Source:');
|
||||
srcLines.forEach(function(source, index) {
|
||||
console.log(index + 1, source);
|
||||
});
|
||||
console.log();
|
||||
console.log('Generated:');
|
||||
console.log(template.code);
|
||||
lines.forEach(function(source, index) {
|
||||
console.log(index + 1, source);
|
||||
});
|
||||
console.log();
|
||||
console.log('Map:');
|
||||
console.log(template.map);
|
||||
console.log();
|
||||
|
||||
function collectSource(lines, lineName, colName, order) {
|
||||
var ret = {},
|
||||
ordered = [],
|
||||
last;
|
||||
|
||||
function collect(current) {
|
||||
if (last) {
|
||||
var mapLines = lines.slice(last[lineName] - 1, current && current[lineName]);
|
||||
if (mapLines.length) {
|
||||
if (current) {
|
||||
mapLines[mapLines.length - 1] = mapLines[mapLines.length - 1].slice(0, current[colName]);
|
||||
}
|
||||
mapLines[0] = mapLines[0].slice(last[colName]);
|
||||
}
|
||||
ret[last[lineName] + ':' + last[colName]] = mapLines.join('\n');
|
||||
ordered.push({
|
||||
startLine: last[lineName],
|
||||
startCol: last[colName],
|
||||
endLine: current && current[lineName]
|
||||
});
|
||||
}
|
||||
last = current;
|
||||
}
|
||||
|
||||
consumer.eachMapping(collect, undefined, order);
|
||||
collect();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
srcLines = collectSource(srcLines, 'originalLine', 'originalColumn', SourceMapConsumer.ORIGINAL_ORDER);
|
||||
lines = collectSource(lines, 'generatedLine', 'generatedColumn');
|
||||
|
||||
consumer.eachMapping(function(mapping) {
|
||||
var originalSrc = srcLines[mapping.originalLine + ':' + mapping.originalColumn],
|
||||
generatedSrc = lines[mapping.generatedLine + ':' + mapping.generatedColumn];
|
||||
|
||||
if (!mapping.originalLine) {
|
||||
console.log('generated', mapping.generatedLine + ':' + mapping.generatedColumn, generatedSrc);
|
||||
} else {
|
||||
console.log('map',
|
||||
mapping.source,
|
||||
mapping.originalLine + ':' + mapping.originalColumn,
|
||||
originalSrc,
|
||||
'->',
|
||||
mapping.generatedLine + ':' + mapping.generatedColumn,
|
||||
generatedSrc);
|
||||
}
|
||||
});
|
||||
}
|
||||
+326
-271
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"../.eslintrc.js",
|
||||
"plugin:es5/no-es2015",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"es5"
|
||||
],
|
||||
"globals": {
|
||||
"CompilerContext": true,
|
||||
"Handlebars": true,
|
||||
"handlebarsEnv": true,
|
||||
"shouldCompileTo": true,
|
||||
"shouldCompileToWithPartials": true,
|
||||
"shouldThrow": true,
|
||||
"expectTemplate": true,
|
||||
"compileWithPartials": true,
|
||||
"console": true,
|
||||
"require": true,
|
||||
"suite": true,
|
||||
"equal": true,
|
||||
"equals": true,
|
||||
"test": true,
|
||||
"testBoth": true,
|
||||
"raises": true,
|
||||
"deepEqual": true,
|
||||
"start": true,
|
||||
"stop": true,
|
||||
"ok": true,
|
||||
"sinon": true,
|
||||
"strictEqual": true,
|
||||
"define": true,
|
||||
"expect": true,
|
||||
"chai": true
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": "off",
|
||||
|
||||
"no-var": "off",
|
||||
"dot-notation": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
CompilerContext: true,
|
||||
Handlebars: true,
|
||||
handlebarsEnv: true,
|
||||
shouldCompileTo: true,
|
||||
shouldCompileToWithPartials: true,
|
||||
shouldThrow: true,
|
||||
expectTemplate: true,
|
||||
compileWithPartials: true,
|
||||
suite: true,
|
||||
equal: true,
|
||||
equals: true,
|
||||
test: true,
|
||||
testBoth: true,
|
||||
raises: true,
|
||||
deepEqual: true,
|
||||
start: true,
|
||||
stop: true,
|
||||
ok: true,
|
||||
sinon: true,
|
||||
strictEqual: true,
|
||||
define: true,
|
||||
expect: true,
|
||||
chai: true,
|
||||
},
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
rules: {
|
||||
// Disabling for tests, for now.
|
||||
'no-path-concat': 'off',
|
||||
|
||||
'no-var': 'off',
|
||||
'dot-notation': 'off',
|
||||
},
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/json2.js"></script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars['default'].template, true);
|
||||
equal(!!Handlebars['default'].VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
// Inject any require initilizer failures into the first test so they are properly
|
||||
// reported.
|
||||
if (requireFailure) {
|
||||
runner.on('hook end', function(hook){
|
||||
if (requireFailure) {
|
||||
runner.uncaught(requireFailure);
|
||||
requireFailure = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/json2.js"></script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars.amd',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
|
||||
require(['handlebars'], function(Handlebars) {
|
||||
window.Handlebars = Handlebars['default'];
|
||||
|
||||
require(['tests'], function(Handlebars) {
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
// Inject any require initilizer failures into the first test so they are properly
|
||||
// reported.
|
||||
if (requireFailure) {
|
||||
runner.on('hook end', function(hook){
|
||||
if (requireFailure) {
|
||||
runner.uncaught(requireFailure);
|
||||
requireFailure = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function logFailure(test, err){
|
||||
function flattenTitles(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
}
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+34
-281
@@ -1,14 +1,14 @@
|
||||
describe('ast', function() {
|
||||
describe('ast', function () {
|
||||
if (!Handlebars.AST) {
|
||||
return;
|
||||
}
|
||||
|
||||
var AST = Handlebars.AST;
|
||||
|
||||
describe('BlockStatement', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
describe('BlockStatement', function () {
|
||||
it('should throw on mustache mismatch', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
},
|
||||
Handlebars.Exception,
|
||||
@@ -17,14 +17,14 @@ describe('ast', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('helpers', function() {
|
||||
describe('#helperExpression', function() {
|
||||
it('should handle mustache statements', function() {
|
||||
describe('helpers', function () {
|
||||
describe('#helperExpression', function () {
|
||||
it('should handle mustache statements', function () {
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: undefined
|
||||
hash: undefined,
|
||||
}),
|
||||
false
|
||||
);
|
||||
@@ -32,7 +32,7 @@ describe('ast', function() {
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [1],
|
||||
hash: undefined
|
||||
hash: undefined,
|
||||
}),
|
||||
true
|
||||
);
|
||||
@@ -40,17 +40,17 @@ describe('ast', function() {
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: {}
|
||||
hash: {},
|
||||
}),
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should handle block statements', function() {
|
||||
it('should handle block statements', function () {
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: undefined
|
||||
hash: undefined,
|
||||
}),
|
||||
false
|
||||
);
|
||||
@@ -58,7 +58,7 @@ describe('ast', function() {
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [1],
|
||||
hash: undefined
|
||||
hash: undefined,
|
||||
}),
|
||||
true
|
||||
);
|
||||
@@ -66,15 +66,15 @@ describe('ast', function() {
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: {}
|
||||
hash: {},
|
||||
}),
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should handle subexpressions', function() {
|
||||
it('should handle subexpressions', function () {
|
||||
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
|
||||
});
|
||||
it('should work with non-helper nodes', function() {
|
||||
it('should work with non-helper nodes', function () {
|
||||
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
|
||||
|
||||
equals(
|
||||
@@ -107,7 +107,7 @@ describe('ast', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Line Numbers', function() {
|
||||
describe('Line Numbers', function () {
|
||||
var ast, body;
|
||||
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
|
||||
@@ -120,59 +120,59 @@ describe('ast', function() {
|
||||
/* eslint-disable no-multi-spaces */
|
||||
ast = Handlebars.parse(
|
||||
'line 1 {{line1Token}}\n' + // 1
|
||||
' line 2 {{line2token}}\n' + // 2
|
||||
' line 3 {{#blockHelperOnLine3}}\n' + // 3
|
||||
'line 4{{line4token}}\n' + // 4
|
||||
'line5{{else}}\n' + // 5
|
||||
'{{line6Token}}\n' + // 6
|
||||
'{{/blockHelperOnLine3}}\n' + // 7
|
||||
'{{#open}}\n' + // 8
|
||||
'{{else inverse}}\n' + // 9
|
||||
'{{else}}\n' + // 10
|
||||
' line 2 {{line2token}}\n' + // 2
|
||||
' line 3 {{#blockHelperOnLine3}}\n' + // 3
|
||||
'line 4{{line4token}}\n' + // 4
|
||||
'line5{{else}}\n' + // 5
|
||||
'{{line6Token}}\n' + // 6
|
||||
'{{/blockHelperOnLine3}}\n' + // 7
|
||||
'{{#open}}\n' + // 8
|
||||
'{{else inverse}}\n' + // 9
|
||||
'{{else}}\n' + // 10
|
||||
'{{/open}}'
|
||||
); // 11
|
||||
/* eslint-enable no-multi-spaces */
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function() {
|
||||
it('gets ContentNode line numbers', function () {
|
||||
var contentNode = body[0];
|
||||
testColumns(contentNode, 1, 1, 0, 7);
|
||||
});
|
||||
|
||||
it('gets MustacheStatement line numbers', function() {
|
||||
it('gets MustacheStatement line numbers', function () {
|
||||
var mustacheNode = body[1];
|
||||
testColumns(mustacheNode, 1, 1, 7, 21);
|
||||
});
|
||||
|
||||
it('gets line numbers correct when newlines appear', function() {
|
||||
it('gets line numbers correct when newlines appear', function () {
|
||||
testColumns(body[2], 1, 2, 21, 8);
|
||||
});
|
||||
|
||||
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||
it('gets MustacheStatement line numbers correct across newlines', function () {
|
||||
var secondMustacheStatement = body[3];
|
||||
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||
});
|
||||
|
||||
it('gets the block helper information correct', function() {
|
||||
it('gets the block helper information correct', function () {
|
||||
var blockHelperNode = body[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers the program of a block helper', function() {
|
||||
it('correctly records the line numbers the program of a block helper', function () {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 31, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
it('correctly records the line numbers of an inverse of a block helper', function () {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 7, 13, 0);
|
||||
});
|
||||
|
||||
it('correctly records the line number of chained inverses', function() {
|
||||
it('correctly records the line number of chained inverses', function () {
|
||||
var chainInverseNode = body[7];
|
||||
|
||||
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
||||
@@ -181,251 +181,4 @@ describe('ast', function() {
|
||||
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('whitespace control', function() {
|
||||
describe('parse', function() {
|
||||
it('mustache', function() {
|
||||
var ast = Handlebars.parse(' {{~comment~}} ');
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
|
||||
it('block statements', function() {
|
||||
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[1].program.body[0].value, 'foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseWithoutProcessing', function() {
|
||||
it('mustache', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
|
||||
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
|
||||
it('block statements', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
' {{# comment~}} \nfoo\n {{~/comment}}'
|
||||
);
|
||||
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone flags', function() {
|
||||
describe('mustache', function() {
|
||||
it('does not mark mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{comment}} ');
|
||||
equals(!!ast.body[0].value, true);
|
||||
equals(!!ast.body[2].value, true);
|
||||
});
|
||||
});
|
||||
describe('blocks - parseWithoutProcessing', function() {
|
||||
it('block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, ' ');
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
it('initial block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{# comment}} \nfoo\n {{/comment}}'
|
||||
),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
});
|
||||
it('mustaches with children', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{# comment}} \n{{foo}}\n {{/comment}}'
|
||||
),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \n');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n ');
|
||||
});
|
||||
it('nested block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, ' \n');
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
});
|
||||
it('column 0 block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, '');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n');
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(body[0].value, '');
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
});
|
||||
describe('partials - parseWithoutProcessing', function() {
|
||||
it('simple partial', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
|
||||
equals(ast.body[1].value, ' ');
|
||||
});
|
||||
it('indented partial', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[1].indent, '');
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented partial as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{> partial }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[1].indent, ' ');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{> partial }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{> partial }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments - parseWithoutProcessing', function() {
|
||||
it('simple comment', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
|
||||
equals(ast.body[1].value, ' ');
|
||||
});
|
||||
it('indented comment', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented comment as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{! comment }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{! comment }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{! comment }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+453
-487
File diff suppressed because it is too large
Load Diff
+339
-388
@@ -1,463 +1,413 @@
|
||||
describe('blocks', function() {
|
||||
it('array', function() {
|
||||
describe('blocks', function () {
|
||||
it('array', function () {
|
||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
world: 'world'
|
||||
};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'Arrays iterate over the contents when not empty'
|
||||
);
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ goodbyes: [], world: 'world' },
|
||||
'cruel world!',
|
||||
'Arrays ignore the contents when empty'
|
||||
);
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbyes: [
|
||||
{ text: 'goodbye' },
|
||||
{ text: 'Goodbye' },
|
||||
{ text: 'GOODBYE' },
|
||||
],
|
||||
world: 'world',
|
||||
})
|
||||
.withMessage('Arrays iterate over the contents when not empty')
|
||||
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbyes: [],
|
||||
world: 'world',
|
||||
})
|
||||
.withMessage('Arrays ignore the contents when empty')
|
||||
.toCompileTo('cruel world!');
|
||||
});
|
||||
|
||||
it('array without data', function() {
|
||||
var string =
|
||||
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
||||
var hash = {
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
world: 'world'
|
||||
};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[hash, , , false],
|
||||
'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
|
||||
);
|
||||
it('array without data', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
|
||||
)
|
||||
.withInput({
|
||||
goodbyes: [
|
||||
{ text: 'goodbye' },
|
||||
{ text: 'Goodbye' },
|
||||
{ text: 'GOODBYE' },
|
||||
],
|
||||
world: 'world',
|
||||
})
|
||||
.withCompileOptions({ compat: false })
|
||||
.toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||
});
|
||||
|
||||
it('array with @index', function() {
|
||||
var string =
|
||||
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
world: 'world'
|
||||
};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(
|
||||
result,
|
||||
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
|
||||
'The @index variable is used'
|
||||
);
|
||||
it('array with @index', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
|
||||
)
|
||||
.withInput({
|
||||
goodbyes: [
|
||||
{ text: 'goodbye' },
|
||||
{ text: 'Goodbye' },
|
||||
{ text: 'GOODBYE' },
|
||||
],
|
||||
world: 'world',
|
||||
})
|
||||
.withMessage('The @index variable is used')
|
||||
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||
});
|
||||
|
||||
it('empty block', function() {
|
||||
it('empty block', function () {
|
||||
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
world: 'world'
|
||||
};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'cruel world!',
|
||||
'Arrays iterate over the contents when not empty'
|
||||
);
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ goodbyes: [], world: 'world' },
|
||||
'cruel world!',
|
||||
'Arrays ignore the contents when empty'
|
||||
);
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbyes: [
|
||||
{ text: 'goodbye' },
|
||||
{ text: 'Goodbye' },
|
||||
{ text: 'GOODBYE' },
|
||||
],
|
||||
world: 'world',
|
||||
})
|
||||
.withMessage('Arrays iterate over the contents when not empty')
|
||||
.toCompileTo('cruel world!');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbyes: [],
|
||||
world: 'world',
|
||||
})
|
||||
.withMessage('Arrays ignore the contents when empty')
|
||||
.toCompileTo('cruel world!');
|
||||
});
|
||||
|
||||
it('block with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
|
||||
var hash = {
|
||||
name: 'Alan',
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
|
||||
'Templates can access variables in contexts up the stack with relative path syntax'
|
||||
);
|
||||
it('block with complex lookup', function () {
|
||||
expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
|
||||
.withInput({
|
||||
name: 'Alan',
|
||||
goodbyes: [
|
||||
{ text: 'goodbye' },
|
||||
{ text: 'Goodbye' },
|
||||
{ text: 'GOODBYE' },
|
||||
],
|
||||
})
|
||||
.withMessage(
|
||||
'Templates can access variables in contexts up the stack with relative path syntax'
|
||||
)
|
||||
.toCompileTo(
|
||||
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple blocks with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {
|
||||
name: 'Alan',
|
||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
||||
it('multiple blocks with complex lookup', function () {
|
||||
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', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
it('block with complex lookup using nested context', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
|
||||
).toThrow(Error);
|
||||
});
|
||||
|
||||
it('block with deep nested complex lookup', function() {
|
||||
var string =
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
|
||||
var hash = {
|
||||
omg: 'OMG!',
|
||||
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
||||
it('block with deep nested complex lookup', function () {
|
||||
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', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
|
||||
{ data: false }
|
||||
);
|
||||
|
||||
var result = template({
|
||||
person: [
|
||||
{ first: 'Alan', last: 'Johnson' },
|
||||
{ first: 'Alan', last: 'Johnson' }
|
||||
]
|
||||
});
|
||||
equals(result, 'Alan JohnsonAlan Johnson');
|
||||
it('works with cached blocks', function () {
|
||||
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', function() {
|
||||
it('inverted sections with unset value', function() {
|
||||
var string =
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'Right On!',
|
||||
"Inverted section rendered when value isn't set."
|
||||
);
|
||||
describe('inverted sections', function () {
|
||||
it('inverted sections with unset value', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||
)
|
||||
.withMessage("Inverted section rendered when value isn't set.")
|
||||
.toCompileTo('Right On!');
|
||||
});
|
||||
|
||||
it('inverted section with false value', function() {
|
||||
var string =
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = { goodbyes: false };
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'Right On!',
|
||||
'Inverted section rendered when value is false.'
|
||||
);
|
||||
it('inverted section with false value', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||
)
|
||||
.withInput({ goodbyes: false })
|
||||
.withMessage('Inverted section rendered when value is false.')
|
||||
.toCompileTo('Right On!');
|
||||
});
|
||||
|
||||
it('inverted section with empty set', function() {
|
||||
var string =
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = { goodbyes: [] };
|
||||
shouldCompileTo(
|
||||
string,
|
||||
hash,
|
||||
'Right On!',
|
||||
'Inverted section rendered when value is empty set.'
|
||||
);
|
||||
it('inverted section with empty set', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||
)
|
||||
.withInput({ goodbyes: [] })
|
||||
.withMessage('Inverted section rendered when value is empty set.')
|
||||
.toCompileTo('Right On!');
|
||||
});
|
||||
|
||||
it('block inverted sections', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
|
||||
{ none: 'No people' },
|
||||
'No people'
|
||||
);
|
||||
});
|
||||
it('chained inverted sections', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
|
||||
{ none: 'No people' },
|
||||
'No people'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
|
||||
{ none: 'No people' },
|
||||
'No people'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
|
||||
{ none: 'No people' },
|
||||
'No people'
|
||||
);
|
||||
});
|
||||
it('chained inverted sections with mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
|
||||
{ none: 'No people' },
|
||||
'No people'
|
||||
);
|
||||
}, Error);
|
||||
it('block inverted sections', function () {
|
||||
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||
.withInput({ none: 'No people' })
|
||||
.toCompileTo('No people');
|
||||
});
|
||||
|
||||
it('block inverted sections with empty arrays', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
|
||||
{ none: 'No people', people: [] },
|
||||
'No people'
|
||||
);
|
||||
it('chained inverted sections', function () {
|
||||
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', function () {
|
||||
expectTemplate(
|
||||
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
|
||||
).toThrow(Error);
|
||||
});
|
||||
|
||||
it('block inverted sections with empty arrays', function () {
|
||||
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||
.withInput({
|
||||
none: 'No people',
|
||||
people: [],
|
||||
})
|
||||
.toCompileTo('No people');
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone sections', function() {
|
||||
it('block standalone else sections', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
||||
{ none: 'No people' },
|
||||
'No people\n'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
|
||||
{ none: 'No people' },
|
||||
'No people\n'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
||||
{ none: 'No people' },
|
||||
'No people\n'
|
||||
);
|
||||
describe('standalone sections', function () {
|
||||
it('block standalone else sections', function () {
|
||||
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 else sections can be disabled', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
||||
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
|
||||
'\nNo people\n\n'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
|
||||
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
|
||||
'\nNo people\n\n'
|
||||
);
|
||||
|
||||
it('block standalone else sections can be disabled', function () {
|
||||
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||
.withInput({ none: 'No people' })
|
||||
.withCompileOptions({ ignoreStandalone: true })
|
||||
.toCompileTo('\nNo people\n\n');
|
||||
|
||||
expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
|
||||
.withInput({ none: 'No people' })
|
||||
.withCompileOptions({ ignoreStandalone: true })
|
||||
.toCompileTo('\nNo people\n\n');
|
||||
});
|
||||
it('block standalone chained else sections', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
|
||||
{ none: 'No people' },
|
||||
'No people\n'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
|
||||
{ none: 'No people' },
|
||||
'No people\n'
|
||||
);
|
||||
|
||||
it('block standalone chained else sections', function () {
|
||||
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', function() {
|
||||
shouldCompileTo(
|
||||
'{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
|
||||
{ data: [1, 3, 5] },
|
||||
'1\n3\n5\nOK.'
|
||||
);
|
||||
|
||||
it('should handle nesting', function () {
|
||||
expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
|
||||
.withInput({
|
||||
data: [1, 3, 5],
|
||||
})
|
||||
.toCompileTo('1\n3\n5\nOK.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compat mode', function() {
|
||||
it('block with deep recursive lookup lookup', function() {
|
||||
var string =
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
|
||||
var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[hash, undefined, undefined, true],
|
||||
'Goodbye cruel OMG!'
|
||||
);
|
||||
describe('compat mode', function () {
|
||||
it('block with deep recursive lookup lookup', function () {
|
||||
expectTemplate(
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
|
||||
)
|
||||
.withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
|
||||
.withCompileOptions({ compat: true })
|
||||
.toCompileTo('Goodbye cruel OMG!');
|
||||
});
|
||||
|
||||
it('block with deep recursive pathed lookup', function() {
|
||||
var string =
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {
|
||||
omg: { yes: 'OMG!' },
|
||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[hash, undefined, undefined, true],
|
||||
'Goodbye cruel OMG!'
|
||||
);
|
||||
it('block with deep recursive pathed lookup', function () {
|
||||
expectTemplate(
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||
)
|
||||
.withInput({
|
||||
omg: { yes: 'OMG!' },
|
||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }],
|
||||
})
|
||||
.withCompileOptions({ compat: true })
|
||||
.toCompileTo('Goodbye cruel OMG!');
|
||||
});
|
||||
it('block with missed recursive lookup', function() {
|
||||
var string =
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {
|
||||
omg: { no: 'OMG!' },
|
||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[hash, undefined, undefined, true],
|
||||
'Goodbye cruel '
|
||||
);
|
||||
it('block with missed recursive lookup', function () {
|
||||
expectTemplate(
|
||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||
)
|
||||
.withInput({
|
||||
omg: { no: 'OMG!' },
|
||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }],
|
||||
})
|
||||
.withCompileOptions({ compat: true })
|
||||
.toCompileTo('Goodbye cruel ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorators', function() {
|
||||
it('should apply mustache decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
describe('decorators', function () {
|
||||
it('should apply mustache decorators', function () {
|
||||
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
})
|
||||
.withDecorator('decorator', function (fn) {
|
||||
fn.run = 'success';
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}{{/helper}}',
|
||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
});
|
||||
it('should apply allow undefined return', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn() + options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
fn.run = 'cess';
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}suc{{/helper}}',
|
||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply block decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
it('should apply allow undefined return', function () {
|
||||
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn() + options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn) {
|
||||
fn.run = 'cess';
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply block decorators', function () {
|
||||
expectTemplate(
|
||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
|
||||
)
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
fn.run = options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
|
||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
});
|
||||
it('should support nested decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.fn.nested + options.fn();
|
||||
return fn;
|
||||
},
|
||||
nested: function(fn, props, container, options) {
|
||||
props.nested = options.fn();
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
|
||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply multiple decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
it('should support nested decorators', function () {
|
||||
expectTemplate(
|
||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
|
||||
)
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
})
|
||||
.withDecorators({
|
||||
decorator: function (fn, props, container, options) {
|
||||
fn.run = options.fn.nested + options.fn();
|
||||
return fn;
|
||||
},
|
||||
nested: function (fn, props, container, options) {
|
||||
props.nested = options.fn();
|
||||
},
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply multiple decorators', function () {
|
||||
expectTemplate(
|
||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
|
||||
)
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
fn.run = (fn.run || '') + options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
|
||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should access parent variables', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
it('should access parent variables', function () {
|
||||
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
fn.run = options.args;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator foo}}{{/helper}}',
|
||||
{ hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
|
||||
'success'
|
||||
);
|
||||
})
|
||||
.withInput({ foo: 'success' })
|
||||
.toCompileTo('success');
|
||||
});
|
||||
it('should work with root program', function() {
|
||||
|
||||
it('should work with root program', function () {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
expectTemplate('{{*decorator "success"}}')
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
equals(options.args[0], 'success');
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator "success"}}',
|
||||
{ hash: { foo: 'success' }, decorators: decorators },
|
||||
''
|
||||
);
|
||||
})
|
||||
.withInput({ foo: 'success' })
|
||||
.toCompileTo('');
|
||||
equals(run, true);
|
||||
});
|
||||
it('should fail when accessing variables from root', function() {
|
||||
|
||||
it('should fail when accessing variables from root', function () {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
expectTemplate('{{*decorator foo}}')
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
equals(options.args[0], undefined);
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator foo}}',
|
||||
{ hash: { foo: 'fail' }, decorators: decorators },
|
||||
''
|
||||
);
|
||||
})
|
||||
.withInput({ foo: 'fail' })
|
||||
.toCompileTo('');
|
||||
equals(run, true);
|
||||
});
|
||||
|
||||
describe('registration', function() {
|
||||
it('unregisters', function() {
|
||||
describe('registration', function () {
|
||||
it('unregisters', function () {
|
||||
handlebarsEnv.decorators = {};
|
||||
|
||||
handlebarsEnv.registerDecorator('foo', function() {
|
||||
handlebarsEnv.registerDecorator('foo', function () {
|
||||
return 'fail';
|
||||
});
|
||||
|
||||
@@ -466,12 +416,12 @@ describe('blocks', function() {
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
});
|
||||
|
||||
it('allows multiple globals', function() {
|
||||
it('allows multiple globals', function () {
|
||||
handlebarsEnv.decorators = {};
|
||||
|
||||
handlebarsEnv.registerDecorator({
|
||||
foo: function() {},
|
||||
bar: function() {}
|
||||
foo: function () {},
|
||||
bar: function () {},
|
||||
});
|
||||
|
||||
equals(!!handlebarsEnv.decorators.foo, true);
|
||||
@@ -481,17 +431,18 @@ describe('blocks', function() {
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
equals(handlebarsEnv.decorators.bar, undefined);
|
||||
});
|
||||
it('fails with multiple and args', function() {
|
||||
|
||||
it('fails with multiple and args', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
handlebarsEnv.registerDecorator(
|
||||
{
|
||||
world: function() {
|
||||
world: function () {
|
||||
return 'world!';
|
||||
},
|
||||
testHelper: function() {
|
||||
testHelper: function () {
|
||||
return 'found it!';
|
||||
}
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
+553
-538
File diff suppressed because it is too large
Load Diff
+41
-24
@@ -1,15 +1,15 @@
|
||||
describe('compiler', function() {
|
||||
describe('compiler', function () {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('#equals', function() {
|
||||
describe('#equals', function () {
|
||||
function compile(string) {
|
||||
var ast = Handlebars.parse(string);
|
||||
return new Handlebars.Compiler().compile(ast, {});
|
||||
}
|
||||
|
||||
it('should treat as equal', function() {
|
||||
it('should treat as equal', function () {
|
||||
equal(compile('foo').equals(compile('foo')), true);
|
||||
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
||||
@@ -30,7 +30,7 @@ describe('compiler', function() {
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should treat as not equal', function() {
|
||||
it('should treat as not equal', function () {
|
||||
equal(compile('foo').equals(compile('bar')), false);
|
||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
||||
@@ -59,17 +59,17 @@ describe('compiler', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
describe('#compile', function () {
|
||||
it('should fail with invalid input', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.compile(null);
|
||||
},
|
||||
Error,
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.compile({});
|
||||
},
|
||||
Error,
|
||||
@@ -77,7 +77,7 @@ describe('compiler', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should include the location in the error (row and column)', function() {
|
||||
it('should include the location in the error (row and column)', function () {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(
|
||||
@@ -101,7 +101,7 @@ describe('compiler', function() {
|
||||
}
|
||||
});
|
||||
|
||||
it('should include the location as enumerable property', function() {
|
||||
it('should include the location as enumerable property', function () {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(
|
||||
@@ -118,21 +118,38 @@ describe('compiler', function() {
|
||||
}
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
it('can utilize AST instance', function () {
|
||||
equal(
|
||||
Handlebars.compile({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }],
|
||||
})(),
|
||||
'Hello'
|
||||
);
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
it('can pass through an empty string', function () {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
});
|
||||
|
||||
it('should not modify the options.data property(GH-1327)', function() {
|
||||
it('throws on desupported options', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile('Dudes', { trackIds: true });
|
||||
},
|
||||
Error,
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile('Dudes', { stringParams: true });
|
||||
},
|
||||
Error,
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify the options.data property(GH-1327)', function () {
|
||||
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(
|
||||
@@ -141,7 +158,7 @@ describe('compiler', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify the options.knownHelpers property(GH-1327)', function() {
|
||||
it('should not modify the options.knownHelpers property(GH-1327)', function () {
|
||||
var options = { knownHelpers: {} };
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(
|
||||
@@ -151,37 +168,37 @@ describe('compiler', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#precompile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
describe('#precompile', function () {
|
||||
it('should fail with invalid input', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.precompile(null);
|
||||
},
|
||||
Error,
|
||||
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.precompile({});
|
||||
},
|
||||
Error,
|
||||
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
|
||||
);
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
it('can utilize AST instance', function () {
|
||||
equal(
|
||||
/return "Hello"/.test(
|
||||
Handlebars.precompile({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }],
|
||||
})
|
||||
),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
it('can pass through an empty string', function () {
|
||||
equal(/return ""/.test(Handlebars.precompile('')), true);
|
||||
});
|
||||
});
|
||||
|
||||
+193
-279
@@ -1,34 +1,27 @@
|
||||
describe('data', function() {
|
||||
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
||||
var template = CompilerContext.compile('{{hello}}', { data: true });
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
describe('data', function () {
|
||||
it('passing in data to a compiled function that expects data - works with helpers', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (options) {
|
||||
return options.data.adjective + ' ' + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ noun: 'cat' },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('happy cat', result, 'Data output by helper');
|
||||
})
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withInput({ noun: 'cat' })
|
||||
.withMessage('Data output by helper')
|
||||
.toCompileTo('happy cat');
|
||||
});
|
||||
|
||||
it('data can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}');
|
||||
var result = template({}, { data: { hello: 'hello' } });
|
||||
equals('hello', result, '@foo retrieves template data');
|
||||
it('data can be looked up via @foo', function () {
|
||||
expectTemplate('{{@hello}}')
|
||||
.withRuntimeOptions({ data: { hello: 'hello' } })
|
||||
.withMessage('@foo retrieves template data')
|
||||
.toCompileTo('hello');
|
||||
});
|
||||
|
||||
it('deep @foo triggers automatic top-level data', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
|
||||
);
|
||||
|
||||
it('deep @foo triggers automatic top-level data', function () {
|
||||
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||
|
||||
helpers.let = function(options) {
|
||||
helpers.let = function (options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
|
||||
for (var prop in options.hash) {
|
||||
@@ -39,124 +32,92 @@ describe('data', function() {
|
||||
return options.fn(this, { data: frame });
|
||||
};
|
||||
|
||||
var result = template({ foo: true }, { helpers: helpers });
|
||||
equals('Hello world', result, 'Automatic data was triggered');
|
||||
expectTemplate(
|
||||
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
|
||||
)
|
||||
.withInput({ foo: true })
|
||||
.withHelpers(helpers)
|
||||
.withMessage('Automatic data was triggered')
|
||||
.toCompileTo('Hello world');
|
||||
});
|
||||
|
||||
it('parameter data can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{hello @world}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
it('parameter data can be looked up via @foo', function () {
|
||||
expectTemplate('{{hello @world}}')
|
||||
.withRuntimeOptions({ data: { world: 'world' } })
|
||||
.withHelper('hello', function (noun) {
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
||||
equals(
|
||||
'Hello world',
|
||||
result,
|
||||
'@foo as a parameter retrieves template data'
|
||||
);
|
||||
})
|
||||
.withMessage('@foo as a parameter retrieves template data')
|
||||
.toCompileTo('Hello world');
|
||||
});
|
||||
|
||||
it('hash values can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{hello noun=@world}}');
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('hash values can be looked up via @foo', function () {
|
||||
expectTemplate('{{hello noun=@world}}')
|
||||
.withRuntimeOptions({ data: { world: 'world' } })
|
||||
.withHelper('hello', function (options) {
|
||||
return 'Hello ' + options.hash.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
||||
equals(
|
||||
'Hello world',
|
||||
result,
|
||||
'@foo as a parameter retrieves template data'
|
||||
);
|
||||
})
|
||||
.withMessage('@foo as a parameter retrieves template data')
|
||||
.toCompileTo('Hello world');
|
||||
});
|
||||
|
||||
it('nested parameter data can be looked up via @foo.bar', function() {
|
||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
it('nested parameter data can be looked up via @foo.bar', function () {
|
||||
expectTemplate('{{hello @world.bar}}')
|
||||
.withRuntimeOptions({ data: { world: { bar: 'world' } } })
|
||||
.withHelper('hello', function (noun) {
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{},
|
||||
{ helpers: helpers, data: { world: { bar: 'world' } } }
|
||||
);
|
||||
equals(
|
||||
'Hello world',
|
||||
result,
|
||||
'@foo as a parameter retrieves template data'
|
||||
);
|
||||
})
|
||||
.withMessage('@foo as a parameter retrieves template data')
|
||||
.toCompileTo('Hello world');
|
||||
});
|
||||
|
||||
it('nested parameter data does not fail with @world.bar', function() {
|
||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
it('nested parameter data does not fail with @world.bar', function () {
|
||||
expectTemplate('{{hello @world.bar}}')
|
||||
.withRuntimeOptions({ data: { foo: { bar: 'world' } } })
|
||||
.withHelper('hello', function (noun) {
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{},
|
||||
{ helpers: helpers, data: { foo: { bar: 'world' } } }
|
||||
);
|
||||
equals(
|
||||
'Hello undefined',
|
||||
result,
|
||||
'@foo as a parameter retrieves template data'
|
||||
);
|
||||
})
|
||||
.withMessage('@foo as a parameter retrieves template data')
|
||||
.toCompileTo('Hello undefined');
|
||||
});
|
||||
|
||||
it('parameter data throws when using complex scope references', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}';
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
it('parameter data throws when using complex scope references', function () {
|
||||
expectTemplate(
|
||||
'{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
|
||||
).toThrow(Error);
|
||||
});
|
||||
|
||||
it('data can be functions', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}');
|
||||
var result = template(
|
||||
{},
|
||||
{
|
||||
it('data can be functions', function () {
|
||||
expectTemplate('{{@hello}}')
|
||||
.withRuntimeOptions({
|
||||
data: {
|
||||
hello: function() {
|
||||
hello: function () {
|
||||
return 'hello';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
equals('hello', result);
|
||||
});
|
||||
it('data can be functions with params', function() {
|
||||
var template = CompilerContext.compile('{{@hello "hello"}}');
|
||||
var result = template(
|
||||
{},
|
||||
{
|
||||
data: {
|
||||
hello: function(arg) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
equals('hello', result);
|
||||
},
|
||||
},
|
||||
})
|
||||
.toCompileTo('hello');
|
||||
});
|
||||
|
||||
it('data is inherited downstream', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
|
||||
{ data: true }
|
||||
);
|
||||
var helpers = {
|
||||
let: function(options) {
|
||||
it('data can be functions with params', function () {
|
||||
expectTemplate('{{@hello "hello"}}')
|
||||
.withRuntimeOptions({
|
||||
data: {
|
||||
hello: function (arg) {
|
||||
return arg;
|
||||
},
|
||||
},
|
||||
})
|
||||
.toCompileTo('hello');
|
||||
});
|
||||
|
||||
it('data is inherited downstream', function () {
|
||||
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 (options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
for (var prop in options.hash) {
|
||||
if (prop in options.hash) {
|
||||
@@ -164,201 +125,154 @@ describe('data', function() {
|
||||
}
|
||||
}
|
||||
return options.fn(this, { data: frame });
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ bar: { baz: 'hello world' } },
|
||||
{ helpers: helpers, data: {} }
|
||||
);
|
||||
equals('2hello world1', result, 'data variables are inherited downstream');
|
||||
})
|
||||
.withRuntimeOptions({ data: {} })
|
||||
.withMessage('data variables are inherited downstream')
|
||||
.toCompileTo('2hello world1');
|
||||
});
|
||||
|
||||
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
||||
var template = CompilerContext.compile('{{>myPartial}}', { data: true });
|
||||
|
||||
var partials = {
|
||||
myPartial: CompilerContext.compile('{{hello}}', { data: true })
|
||||
};
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('passing in data to a compiled function that expects data - works with helpers in partials', function () {
|
||||
expectTemplate('{{>myPartial}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withPartial('myPartial', '{{hello}}')
|
||||
.withHelper('hello', function (options) {
|
||||
return options.data.adjective + ' ' + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ noun: 'cat' },
|
||||
{ helpers: helpers, partials: partials, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('happy cat', result, 'Data output by helper inside partial');
|
||||
})
|
||||
.withInput({ noun: 'cat' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Data output by helper inside partial')
|
||||
.toCompileTo('happy cat');
|
||||
});
|
||||
|
||||
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
|
||||
var template = CompilerContext.compile('{{hello world}}', { data: true });
|
||||
|
||||
var helpers = {
|
||||
hello: function(noun, options) {
|
||||
it('passing in data to a compiled function that expects data - works with helpers and parameters', function () {
|
||||
expectTemplate('{{hello world}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (noun, options) {
|
||||
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true, world: 'world' },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('happy world!', result, 'Data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true, world: 'world' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Data output by helper')
|
||||
.toCompileTo('happy world!');
|
||||
});
|
||||
|
||||
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {
|
||||
data: true
|
||||
});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('passing in data to a compiled function that expects data - works with block helpers', function () {
|
||||
expectTemplate('{{#hello}}{{world}}{{/hello}}')
|
||||
.withCompileOptions({
|
||||
data: true,
|
||||
})
|
||||
.withHelper('hello', function (options) {
|
||||
return options.fn(this);
|
||||
},
|
||||
world: function(options) {
|
||||
})
|
||||
.withHelper('world', function (options) {
|
||||
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('happy world!', result, 'Data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Data output by helper')
|
||||
.toCompileTo('happy world!');
|
||||
});
|
||||
|
||||
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
||||
{ data: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function () {
|
||||
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (options) {
|
||||
return options.fn({ exclaim: '?' });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
})
|
||||
.withHelper('world', function (thing, options) {
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true, zomg: 'world' },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('happy world?', result, 'Data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true, zomg: 'world' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Data output by helper')
|
||||
.toCompileTo('happy world?');
|
||||
});
|
||||
|
||||
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
||||
{ data: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function () {
|
||||
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (options) {
|
||||
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
})
|
||||
.withHelper('world', function (thing, options) {
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true, zomg: 'world' },
|
||||
{ helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
|
||||
);
|
||||
equals('#win happy world?', result, 'Data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true, zomg: 'world' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
|
||||
.withMessage('Data output by helper')
|
||||
.toCompileTo('#win happy world?');
|
||||
});
|
||||
|
||||
it('you can override inherited data when invoking a helper', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#hello}}{{world zomg}}{{/hello}}',
|
||||
{ data: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('you can override inherited data when invoking a helper', function () {
|
||||
expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (options) {
|
||||
return options.fn(
|
||||
{ exclaim: '?', zomg: 'world' },
|
||||
{ data: { adjective: 'sad' } }
|
||||
);
|
||||
},
|
||||
world: function(thing, options) {
|
||||
})
|
||||
.withHelper('world', function (thing, options) {
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true, zomg: 'planet' },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('sad world?', result, 'Overriden data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true, zomg: 'planet' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Overridden data output by helper')
|
||||
.toCompileTo('sad world?');
|
||||
});
|
||||
|
||||
it('you can override inherited data when invoking a helper with depth', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
||||
{ data: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
it('you can override inherited data when invoking a helper with depth', function () {
|
||||
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||
.withCompileOptions({ data: true })
|
||||
.withHelper('hello', function (options) {
|
||||
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
})
|
||||
.withHelper('world', function (thing, options) {
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{ exclaim: true, zomg: 'world' },
|
||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
||||
);
|
||||
equals('sad world?', result, 'Overriden data output by helper');
|
||||
})
|
||||
.withInput({ exclaim: true, zomg: 'world' })
|
||||
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||
.withMessage('Overridden data output by helper')
|
||||
.toCompileTo('sad world?');
|
||||
});
|
||||
|
||||
describe('@root', function() {
|
||||
it('the root context can be looked up via @root', function() {
|
||||
var template = CompilerContext.compile('{{@root.foo}}');
|
||||
var result = template({ foo: 'hello' }, { data: {} });
|
||||
equals('hello', result);
|
||||
describe('@root', function () {
|
||||
it('the root context can be looked up via @root', function () {
|
||||
expectTemplate('{{@root.foo}}')
|
||||
.withInput({ foo: 'hello' })
|
||||
.withRuntimeOptions({ data: {} })
|
||||
.toCompileTo('hello');
|
||||
|
||||
result = template({ foo: 'hello' }, {});
|
||||
equals('hello', result);
|
||||
expectTemplate('{{@root.foo}}')
|
||||
.withInput({ foo: 'hello' })
|
||||
.toCompileTo('hello');
|
||||
});
|
||||
it('passed root values take priority', function() {
|
||||
var template = CompilerContext.compile('{{@root.foo}}');
|
||||
var result = template({}, { data: { root: { foo: 'hello' } } });
|
||||
equals('hello', result);
|
||||
|
||||
it('passed root values take priority', function () {
|
||||
expectTemplate('{{@root.foo}}')
|
||||
.withInput({ foo: 'should not be used' })
|
||||
.withRuntimeOptions({ data: { root: { foo: 'hello' } } })
|
||||
.toCompileTo('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nesting', function() {
|
||||
it('the root context can be looked up via @root', function() {
|
||||
var template = CompilerContext.compile(
|
||||
describe('nesting', function () {
|
||||
it('the root context can be looked up via @root', function () {
|
||||
expectTemplate(
|
||||
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
|
||||
);
|
||||
var result = template(
|
||||
{ foo: 'hello' },
|
||||
{
|
||||
helpers: {
|
||||
helper: function(options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
frame.depth = options.data.depth + 1;
|
||||
return options.fn(this, { data: frame });
|
||||
}
|
||||
},
|
||||
)
|
||||
.withInput({ foo: 'hello' })
|
||||
.withHelper('helper', function (options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
frame.depth = options.data.depth + 1;
|
||||
return options.fn(this, { data: frame });
|
||||
})
|
||||
.withRuntimeOptions({
|
||||
data: {
|
||||
depth: 0
|
||||
}
|
||||
}
|
||||
);
|
||||
equals('2 1 0', result);
|
||||
depth: 0,
|
||||
},
|
||||
})
|
||||
.toCompileTo('2 1 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+3
-3
@@ -26,13 +26,13 @@ vm.runInThisContext(distHandlebars, filename);
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function(template, options) {
|
||||
compile: function (template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
compileWithPartial: function (template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
|
||||
Vendored
+56
-23
@@ -1,4 +1,4 @@
|
||||
var global = (function() {
|
||||
var global = (function () {
|
||||
return this;
|
||||
})();
|
||||
|
||||
@@ -21,7 +21,7 @@ if (Error.captureStackTrace) {
|
||||
/**
|
||||
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||
*/
|
||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||
global.shouldCompileTo = function (string, hashOrArray, expected, message) {
|
||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
|
||||
/**
|
||||
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||
*/
|
||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
global.compileWithPartials = function (string, hashOrArray, partials) {
|
||||
var template, ary, options;
|
||||
if (hashOrArray && hashOrArray.hash) {
|
||||
ary = [hashOrArray.hash, hashOrArray];
|
||||
@@ -92,7 +92,7 @@ global.equals = global.equal = function equals(a, b, msg) {
|
||||
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||
* @see https://www.chaijs.com/api/bdd/#method_throw
|
||||
*/
|
||||
global.shouldThrow = function(callback, type, msg) {
|
||||
global.shouldThrow = function (callback, type, msg) {
|
||||
var failed;
|
||||
try {
|
||||
callback();
|
||||
@@ -121,7 +121,7 @@ global.shouldThrow = function(callback, type, msg) {
|
||||
}
|
||||
};
|
||||
|
||||
global.expectTemplate = function(templateAsString) {
|
||||
global.expectTemplate = function (templateAsString) {
|
||||
return new HandlebarsTestBench(templateAsString);
|
||||
};
|
||||
|
||||
@@ -129,6 +129,7 @@ function HandlebarsTestBench(templateAsString) {
|
||||
this.templateAsString = templateAsString;
|
||||
this.helpers = {};
|
||||
this.partials = {};
|
||||
this.decorators = {};
|
||||
this.input = {};
|
||||
this.message =
|
||||
'Template' + templateAsString + ' does not evaluate to expected output';
|
||||
@@ -136,53 +137,84 @@ function HandlebarsTestBench(templateAsString) {
|
||||
this.runtimeOptions = {};
|
||||
}
|
||||
|
||||
HandlebarsTestBench.prototype.withInput = function(input) {
|
||||
HandlebarsTestBench.prototype.withInput = function (input) {
|
||||
this.input = input;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
|
||||
HandlebarsTestBench.prototype.withHelper = function (name, helperFunction) {
|
||||
this.helpers[name] = helperFunction;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
|
||||
HandlebarsTestBench.prototype.withHelpers = function (helperFunctions) {
|
||||
var self = this;
|
||||
Object.keys(helperFunctions).forEach(function (name) {
|
||||
self.withHelper(name, helperFunctions[name]);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withPartial = function (name, partialAsString) {
|
||||
this.partials[name] = partialAsString;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
|
||||
HandlebarsTestBench.prototype.withPartials = function (partials) {
|
||||
var self = this;
|
||||
Object.keys(partials).forEach(function (name) {
|
||||
self.withPartial(name, partials[name]);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withDecorator = function (
|
||||
name,
|
||||
decoratorFunction
|
||||
) {
|
||||
this.decorators[name] = decoratorFunction;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withDecorators = function (decorators) {
|
||||
var self = this;
|
||||
Object.keys(decorators).forEach(function (name) {
|
||||
self.withDecorator(name, decorators[name]);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withCompileOptions = function (compileOptions) {
|
||||
this.compileOptions = compileOptions;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
|
||||
HandlebarsTestBench.prototype.withRuntimeOptions = function (runtimeOptions) {
|
||||
this.runtimeOptions = runtimeOptions;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.withMessage = function(message) {
|
||||
HandlebarsTestBench.prototype.withMessage = function (message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
|
||||
expect(this._compileAndExecute()).to.equal(expectedOutputAsString);
|
||||
HandlebarsTestBench.prototype.toCompileTo = function (expectedOutputAsString) {
|
||||
expect(this._compileAndExecute()).to.equal(
|
||||
expectedOutputAsString,
|
||||
this.message
|
||||
);
|
||||
};
|
||||
|
||||
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||
HandlebarsTestBench.prototype.toThrow = function(
|
||||
errorLike,
|
||||
errMsgMatcher,
|
||||
msg
|
||||
) {
|
||||
HandlebarsTestBench.prototype.toThrow = function (errorLike, errMsgMatcher) {
|
||||
var self = this;
|
||||
expect(function() {
|
||||
expect(function () {
|
||||
self._compileAndExecute();
|
||||
}).to.throw(errorLike, errMsgMatcher, msg);
|
||||
}).to.throw(errorLike, errMsgMatcher, this.message);
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype._compileAndExecute = function() {
|
||||
HandlebarsTestBench.prototype._compileAndExecute = function () {
|
||||
var compile =
|
||||
Object.keys(this.partials).length > 0
|
||||
? CompilerContext.compileWithPartial
|
||||
@@ -194,13 +226,14 @@ HandlebarsTestBench.prototype._compileAndExecute = function() {
|
||||
return template(this.input, combinedRuntimeOptions);
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
|
||||
HandlebarsTestBench.prototype._combineRuntimeOptions = function () {
|
||||
var self = this;
|
||||
var combinedRuntimeOptions = {};
|
||||
Object.keys(this.runtimeOptions).forEach(function(key) {
|
||||
Object.keys(this.runtimeOptions).forEach(function (key) {
|
||||
combinedRuntimeOptions[key] = self.runtimeOptions[key];
|
||||
});
|
||||
combinedRuntimeOptions.helpers = this.helpers;
|
||||
combinedRuntimeOptions.partials = this.partials;
|
||||
combinedRuntimeOptions.decorators = this.decorators;
|
||||
return combinedRuntimeOptions;
|
||||
};
|
||||
|
||||
Vendored
+3
-3
@@ -11,13 +11,13 @@ global.sinon = require('sinon');
|
||||
global.Handlebars = require('../../lib');
|
||||
|
||||
global.CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
compile: function (template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
compileWithPartial: function (template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
|
||||
Vendored
+9
-9
@@ -15,25 +15,25 @@ if (grep === '--min') {
|
||||
|
||||
var files = fs
|
||||
.readdirSync(testDir)
|
||||
.filter(function(name) {
|
||||
.filter(function (name) {
|
||||
return /.*\.js$/.test(name);
|
||||
})
|
||||
.map(function(name) {
|
||||
.map(function (name) {
|
||||
return testDir + path.sep + name;
|
||||
});
|
||||
|
||||
if (global.minimizedTest) {
|
||||
run('./runtime', function() {
|
||||
run('./browser', function() {
|
||||
run('./runtime', function () {
|
||||
run('./browser', function () {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/* eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
} else {
|
||||
run('./runtime', function() {
|
||||
run('./browser', function() {
|
||||
run('./node', function() {
|
||||
run('./runtime', function () {
|
||||
run('./browser', function () {
|
||||
run('./node', function () {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/* eslint-enable no-process-exit */
|
||||
@@ -50,13 +50,13 @@ function run(env, callback) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
files.forEach(function(name) {
|
||||
files.forEach(function (name) {
|
||||
delete require.cache[name];
|
||||
});
|
||||
|
||||
console.log('Running env: ' + env);
|
||||
require(env);
|
||||
mocha.run(function(errorCount) {
|
||||
mocha.run(function (errorCount) {
|
||||
errors += errorCount;
|
||||
callback();
|
||||
});
|
||||
|
||||
Vendored
+9
-6
@@ -22,16 +22,19 @@ vm.runInThisContext(
|
||||
filename
|
||||
);
|
||||
|
||||
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||
var parse = require('@handlebars/parser').parse;
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function(template, options) {
|
||||
compile: function (template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
|
||||
handlebarsEnv.precompile = function (
|
||||
precompileTemplate,
|
||||
precompileOptions
|
||||
) {
|
||||
return compiler.precompile(
|
||||
precompileTemplate,
|
||||
precompileOptions,
|
||||
@@ -45,9 +48,9 @@ global.CompilerContext = {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
compileWithPartial: function (template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.compile = function(compileTemplate, compileOptions) {
|
||||
handlebarsEnv.compile = function (compileTemplate, compileOptions) {
|
||||
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
@@ -55,7 +58,7 @@ global.CompilerContext = {
|
||||
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
|
||||
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars [template|directory]...
|
||||
Usage: handlebars.js [template|directory]...
|
||||
|
||||
Options:
|
||||
--help Outputs this message [boolean]
|
||||
-f, --output Output File [string]
|
||||
--map Source Map File [string]
|
||||
-a, --amd Exports amd style (require.js) [boolean]
|
||||
--esm Exports ECMAScript module style, path to Handlebars module (eg. "handlebars/lib/handlebars")
|
||||
[string] [default: null]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string] [default: null]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string] [default: ""]
|
||||
-a, --amd Exports AMD style (require.js) [deprecated] [boolean]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for AMD-style) [deprecated] [string] [default: ""]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
|
||||
+642
-937
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -28,7 +28,6 @@
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/dist/handlebars.js"></script>
|
||||
<script src="/spec/vendor/json2.js"></script>
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var CompilerContext = {
|
||||
@@ -62,7 +61,7 @@
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
// Reporting to test-runner
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
|
||||
+45
-33
@@ -1,94 +1,106 @@
|
||||
describe('javascript-compiler api', function() {
|
||||
describe('javascript-compiler api', function () {
|
||||
if (!Handlebars.JavaScriptCompiler) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('#nameLookup', function() {
|
||||
describe('#nameLookup', function () {
|
||||
var $superName;
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
$superName = handlebarsEnv.JavaScriptCompiler.prototype.nameLookup;
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = $superName;
|
||||
});
|
||||
|
||||
it('should allow override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(
|
||||
it('should allow override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function (
|
||||
parent,
|
||||
name
|
||||
) {
|
||||
return parent + '.bar_' + name;
|
||||
};
|
||||
/* eslint-disable camelcase */
|
||||
shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
|
||||
expectTemplate('{{foo}}')
|
||||
.withInput({ bar_foo: 'food' })
|
||||
.toCompileTo('food');
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
||||
// to avoid errors in older browsers.
|
||||
it('should handle reserved words', function() {
|
||||
shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food');
|
||||
it('should handle reserved words', function () {
|
||||
expectTemplate('{{foo}} {{~null~}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('food');
|
||||
});
|
||||
});
|
||||
describe('#compilerInfo', function() {
|
||||
describe('#compilerInfo', function () {
|
||||
var $superCheck, $superInfo;
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
$superCheck = handlebarsEnv.VM.checkRevision;
|
||||
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
handlebarsEnv.VM.checkRevision = $superCheck;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
|
||||
});
|
||||
it('should allow compilerInfo override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
|
||||
it('should allow compilerInfo override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function () {
|
||||
return 'crazy';
|
||||
};
|
||||
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
|
||||
handlebarsEnv.VM.checkRevision = function (compilerInfo) {
|
||||
if (compilerInfo !== 'crazy') {
|
||||
throw new Error("It didn't work");
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
|
||||
expectTemplate('{{foo}} ')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('food ');
|
||||
});
|
||||
});
|
||||
describe('buffer', function() {
|
||||
describe('buffer', function () {
|
||||
var $superAppend, $superCreate;
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
|
||||
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
|
||||
$superCreate =
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer =
|
||||
$superCreate;
|
||||
});
|
||||
|
||||
it('should allow init buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
|
||||
return this.quotedString('foo_');
|
||||
};
|
||||
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
|
||||
it('should allow init buffer override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer =
|
||||
function () {
|
||||
return this.quotedString('foo_');
|
||||
};
|
||||
expectTemplate('{{foo}} ')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('foo_food ');
|
||||
});
|
||||
it('should allow append buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
|
||||
it('should allow append buffer override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function (
|
||||
string
|
||||
) {
|
||||
return $superAppend.call(this, [string, ' + "_foo"']);
|
||||
};
|
||||
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
|
||||
expectTemplate('{{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('food_foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isValidJavaScriptVariableName', function() {
|
||||
describe('#isValidJavaScriptVariableName', function () {
|
||||
// It is there and accessible and could be used by someone. That's why we don't remove it
|
||||
// it 4.x. But if we keep it, we add a test
|
||||
// This test should not encourage you to use the function. It is not needed any more
|
||||
// and might be removed in 5.0
|
||||
['test', 'abc123', 'abc_123'].forEach(function(validVariableName) {
|
||||
it("should return true for '" + validVariableName + "'", function() {
|
||||
['test', 'abc123', 'abc_123'].forEach(function (validVariableName) {
|
||||
it("should return true for '" + validVariableName + "'", function () {
|
||||
expect(
|
||||
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
|
||||
validVariableName
|
||||
@@ -96,8 +108,8 @@ describe('javascript-compiler api', function() {
|
||||
).to.be.true();
|
||||
});
|
||||
});
|
||||
[('123test', 'abc()', 'abc.cde')].forEach(function(invalidVariableName) {
|
||||
it("should return true for '" + invalidVariableName + "'", function() {
|
||||
[('123test', 'abc()', 'abc.cde')].forEach(function (invalidVariableName) {
|
||||
it("should return true for '" + invalidVariableName + "'", function () {
|
||||
expect(
|
||||
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
|
||||
invalidVariableName
|
||||
|
||||
-455
@@ -1,455 +0,0 @@
|
||||
describe('parser', function() {
|
||||
if (!Handlebars.print) {
|
||||
return;
|
||||
}
|
||||
|
||||
function astFor(template) {
|
||||
var ast = Handlebars.parse(template);
|
||||
return Handlebars.print(ast);
|
||||
}
|
||||
|
||||
it('parses simple mustaches', function() {
|
||||
equals(astFor('{{123}}'), '{{ NUMBER{123} [] }}\n');
|
||||
equals(astFor('{{"foo"}}'), '{{ "foo" [] }}\n');
|
||||
equals(astFor('{{false}}'), '{{ BOOLEAN{false} [] }}\n');
|
||||
equals(astFor('{{true}}'), '{{ BOOLEAN{true} [] }}\n');
|
||||
equals(astFor('{{foo}}'), '{{ PATH:foo [] }}\n');
|
||||
equals(astFor('{{foo?}}'), '{{ PATH:foo? [] }}\n');
|
||||
equals(astFor('{{foo_}}'), '{{ PATH:foo_ [] }}\n');
|
||||
equals(astFor('{{foo-}}'), '{{ PATH:foo- [] }}\n');
|
||||
equals(astFor('{{foo:}}'), '{{ PATH:foo: [] }}\n');
|
||||
});
|
||||
|
||||
it('parses simple mustaches with data', function() {
|
||||
equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses simple mustaches with data paths', function() {
|
||||
equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with paths', function() {
|
||||
equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with this/foo', function() {
|
||||
equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with - in a path', function() {
|
||||
equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
|
||||
});
|
||||
it('parses mustaches with escaped [] in a path', function() {
|
||||
equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n');
|
||||
});
|
||||
it('parses escaped \\\\ in path', function() {
|
||||
equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with parameters', function() {
|
||||
equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with string parameters', function() {
|
||||
equals(astFor('{{foo bar "baz" }}'), '{{ PATH:foo [PATH:bar, "baz"] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with NUMBER parameters', function() {
|
||||
equals(astFor('{{foo 1}}'), '{{ PATH:foo [NUMBER{1}] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with BOOLEAN parameters', function() {
|
||||
equals(astFor('{{foo true}}'), '{{ PATH:foo [BOOLEAN{true}] }}\n');
|
||||
equals(astFor('{{foo false}}'), '{{ PATH:foo [BOOLEAN{false}] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with undefined and null paths', function() {
|
||||
equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n');
|
||||
equals(astFor('{{null}}'), '{{ NULL [] }}\n');
|
||||
});
|
||||
it('parses mustaches with undefined and null parameters', function() {
|
||||
equals(
|
||||
astFor('{{foo undefined null}}'),
|
||||
'{{ PATH:foo [UNDEFINED, NULL] }}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses mustaches with DATA parameters', function() {
|
||||
equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with hash arguments', function() {
|
||||
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
|
||||
equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
|
||||
equals(
|
||||
astFor('{{foo bar=true}}'),
|
||||
'{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo bar=false}}'),
|
||||
'{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo bar=@baz}}'),
|
||||
'{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
|
||||
);
|
||||
|
||||
equals(
|
||||
astFor('{{foo bar=baz bat=bam}}'),
|
||||
'{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo bar=baz bat="bam"}}'),
|
||||
'{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
|
||||
);
|
||||
|
||||
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
|
||||
|
||||
equals(
|
||||
astFor('{{foo omg bar=baz bat="bam"}}'),
|
||||
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
|
||||
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
|
||||
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
|
||||
);
|
||||
equals(
|
||||
astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
|
||||
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses contents followed by a mustache', function() {
|
||||
equals(
|
||||
astFor('foo bar {{baz}}'),
|
||||
"CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a partial', function() {
|
||||
equals(astFor('{{> foo }}'), '{{> PARTIAL:foo }}\n');
|
||||
equals(astFor('{{> "foo" }}'), '{{> PARTIAL:foo }}\n');
|
||||
equals(astFor('{{> 1 }}'), '{{> PARTIAL:1 }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with context', function() {
|
||||
equals(astFor('{{> foo bar}}'), '{{> PARTIAL:foo PATH:bar }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with hash', function() {
|
||||
equals(
|
||||
astFor('{{> foo bar=bat}}'),
|
||||
'{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a partial with context and hash', function() {
|
||||
equals(
|
||||
astFor('{{> foo bar bat=baz}}'),
|
||||
'{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a partial with a complex name', function() {
|
||||
equals(
|
||||
astFor('{{> shared/partial?.bar}}'),
|
||||
'{{> PARTIAL:shared/partial?.bar }}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parsers partial blocks', function() {
|
||||
equals(
|
||||
astFor('{{#> foo}}bar{{/foo}}'),
|
||||
"{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
|
||||
);
|
||||
});
|
||||
it('should handle parser block mismatch', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{#> goodbyes}}{{/hellos}}');
|
||||
},
|
||||
Error,
|
||||
/goodbyes doesn't match hellos/
|
||||
);
|
||||
});
|
||||
it('parsers partial blocks with arguments', function() {
|
||||
equals(
|
||||
astFor('{{#> foo context hash=value}}bar{{/foo}}'),
|
||||
"{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a comment', function() {
|
||||
equals(
|
||||
astFor('{{! this is a comment }}'),
|
||||
"{{! ' this is a comment ' }}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a multi-line comment', function() {
|
||||
equals(
|
||||
astFor('{{!\nthis is a multi-line comment\n}}'),
|
||||
"{{! '\nthis is a multi-line comment\n' }}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses an inverse section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses an inverse (else-style) section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses multiple inverse sections', function() {
|
||||
equals(
|
||||
astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses empty blocks', function() {
|
||||
equals(astFor('{{#foo}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n');
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}}{{^}}{{/foo}}'),
|
||||
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse (else-style) section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}}{{else}}{{/foo}}'),
|
||||
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}} bar {{^}}{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse (else-style) section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}} bar {{else}}{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}}{{^}} bar {{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse (else-style) section', function() {
|
||||
equals(
|
||||
astFor('{{#foo}}{{else}} bar {{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses a standalone inverse section', function() {
|
||||
equals(
|
||||
astFor('{{^foo}}bar{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
|
||||
);
|
||||
});
|
||||
it('throws on old inverse section', function() {
|
||||
shouldThrow(function() {
|
||||
astFor('{{else foo}}bar{{/foo}}');
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('parses block with block params', function() {
|
||||
equals(
|
||||
astFor('{{#foo as |bar baz|}}content{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses inverse block with block params', function() {
|
||||
equals(
|
||||
astFor('{{^foo as |bar baz|}}content{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
|
||||
);
|
||||
});
|
||||
it('parses chained inverse block with block params', function() {
|
||||
equals(
|
||||
astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
|
||||
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
|
||||
);
|
||||
});
|
||||
it("raises if there's a Parse error", function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('foo{{^}}bar');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 1/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{foo}');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 1/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{foo &}}');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 1/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{#goodbyes}}{{/hellos}}');
|
||||
},
|
||||
Error,
|
||||
/goodbyes doesn't match hellos/
|
||||
);
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
|
||||
},
|
||||
Error,
|
||||
/goodbyes doesn't match hellos/
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle invalid paths', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{foo/../bar}}');
|
||||
},
|
||||
Error,
|
||||
/Invalid path: foo\/\.\. - 1:2/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{foo/./bar}}');
|
||||
},
|
||||
Error,
|
||||
/Invalid path: foo\/\. - 1:2/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{foo/this/bar}}');
|
||||
},
|
||||
Error,
|
||||
/Invalid path: foo\/this - 1:2/
|
||||
);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('hello\nmy\n{{foo}');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 3/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('hello\n\nmy\n\n{{foo}');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 5/
|
||||
);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors when the first character is a newline', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('\n\nhello\n\nmy\n\n{{foo}');
|
||||
},
|
||||
Error,
|
||||
/Parse error on line 7/
|
||||
);
|
||||
});
|
||||
|
||||
describe('externally compiled AST', function() {
|
||||
it('can pass through an already-compiled AST', function() {
|
||||
equals(
|
||||
astFor({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||
}),
|
||||
"CONTENT[ 'Hello' ]\n"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('directives', function() {
|
||||
it('should parse block directives', function() {
|
||||
equals(
|
||||
astFor('{{#* foo}}{{/foo}}'),
|
||||
'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
|
||||
);
|
||||
});
|
||||
it('should parse directives', function() {
|
||||
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
|
||||
});
|
||||
it('should fail if directives have inverse', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
astFor('{{#* foo}}{{^}}{{/foo}}');
|
||||
},
|
||||
Error,
|
||||
/Unexpected inverse/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('GH1024 - should track program location properly', function() {
|
||||
var p = Handlebars.parse(
|
||||
'\n' +
|
||||
' {{#if foo}}\n' +
|
||||
' {{bar}}\n' +
|
||||
' {{else}} {{baz}}\n' +
|
||||
'\n' +
|
||||
' {{/if}}\n' +
|
||||
' '
|
||||
);
|
||||
|
||||
// We really need a deep equals but for now this should be stable...
|
||||
equals(
|
||||
JSON.stringify(p.loc),
|
||||
JSON.stringify({
|
||||
start: { line: 1, column: 0 },
|
||||
end: { line: 7, column: 4 }
|
||||
})
|
||||
);
|
||||
equals(
|
||||
JSON.stringify(p.body[1].program.loc),
|
||||
JSON.stringify({
|
||||
start: { line: 2, column: 13 },
|
||||
end: { line: 4, column: 7 }
|
||||
})
|
||||
);
|
||||
equals(
|
||||
JSON.stringify(p.body[1].inverse.loc),
|
||||
JSON.stringify({
|
||||
start: { line: 4, column: 15 },
|
||||
end: { line: 6, column: 5 }
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
+529
-693
File diff suppressed because it is too large
Load Diff
+90
-83
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
describe('precompiler', function() {
|
||||
describe('precompiler', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
@@ -19,7 +19,7 @@ describe('precompiler', function() {
|
||||
emptyTemplate = {
|
||||
path: __dirname + '/artifacts/empty.handlebars',
|
||||
name: 'empty',
|
||||
source: ''
|
||||
source: '',
|
||||
},
|
||||
file,
|
||||
content,
|
||||
@@ -38,7 +38,7 @@ describe('precompiler', function() {
|
||||
var _resolveFilename = Module._resolveFilename;
|
||||
delete require.cache[require.resolve('uglify-js')];
|
||||
delete require.cache[require.resolve('../dist/cjs/precompiler')];
|
||||
Module._resolveFilename = function(request, mod) {
|
||||
Module._resolveFilename = function (request, mod) {
|
||||
if (request === 'uglify-js') {
|
||||
throw loadError;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ describe('precompiler', function() {
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
precompile = Handlebars.precompile;
|
||||
minify = uglify.minify;
|
||||
writeFileSync = fs.writeFileSync;
|
||||
@@ -61,21 +61,21 @@ describe('precompiler', function() {
|
||||
// Mock stdout and stderr
|
||||
logFunction = console.log;
|
||||
log = '';
|
||||
console.log = function() {
|
||||
console.log = function () {
|
||||
log += Array.prototype.join.call(arguments, '');
|
||||
};
|
||||
errorLogFunction = console.error;
|
||||
errorLog = '';
|
||||
console.error = function() {
|
||||
console.error = function () {
|
||||
errorLog += Array.prototype.join.call(arguments, '');
|
||||
};
|
||||
|
||||
fs.writeFileSync = function(_file, _content) {
|
||||
fs.writeFileSync = function (_file, _content) {
|
||||
file = _file;
|
||||
content = _content;
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
Handlebars.precompile = precompile;
|
||||
uglify.minify = minify;
|
||||
fs.writeFileSync = writeFileSync;
|
||||
@@ -83,59 +83,59 @@ describe('precompiler', function() {
|
||||
console.error = errorLogFunction;
|
||||
});
|
||||
|
||||
it('should output version', function() {
|
||||
it('should output version', function () {
|
||||
Precompiler.cli({ templates: [], version: true });
|
||||
equals(log, Handlebars.VERSION);
|
||||
});
|
||||
it('should throw if lacking templates', function() {
|
||||
it('should throw if lacking templates', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Precompiler.cli({ templates: [] });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Must define at least one template or directory.'
|
||||
);
|
||||
});
|
||||
it('should handle empty/filtered directories', function() {
|
||||
it('should handle empty/filtered directories', function () {
|
||||
Precompiler.cli({ hasDirectory: true, templates: [] });
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should throw when combining simple and minimized', function() {
|
||||
it('should throw when combining simple and minimized', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Precompiler.cli({ templates: [__dirname], simple: true, min: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Unable to minimize simple output'
|
||||
);
|
||||
});
|
||||
it('should throw when combining simple and multiple templates', function() {
|
||||
it('should throw when combining simple and multiple templates', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Precompiler.cli({
|
||||
templates: [
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
__dirname + '/artifacts/empty.handlebars'
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
],
|
||||
simple: true
|
||||
simple: true,
|
||||
});
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Unable to output multiple templates in simple mode'
|
||||
);
|
||||
});
|
||||
it('should throw when missing name', function() {
|
||||
it('should throw when missing name', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Precompiler.cli({ templates: [{ source: '' }], amd: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Name missing for template'
|
||||
);
|
||||
});
|
||||
it('should throw when combining simple and directories', function() {
|
||||
it('should throw when combining simple and directories', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
@@ -143,70 +143,77 @@ describe('precompiler', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should output simple templates', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output simple templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], simple: true });
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should default to simple templates', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should default to simple templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [{ source: '' }] });
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output amd templates', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output amd templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], amd: true });
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output multiple amd', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
namespace: 'foo'
|
||||
namespace: 'foo',
|
||||
});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output amd partials', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output amd partials', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd partials', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output multiple amd partials', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
partial: true
|
||||
partial: true,
|
||||
});
|
||||
equal(/return Handlebars\.partials\[/.test(log), false);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output es module templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'esm';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], esm: true });
|
||||
equal(/template\(esm\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'commonjs';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
|
||||
equal(/template\(commonjs\)/.test(log), true);
|
||||
});
|
||||
|
||||
it('should set data flag', function() {
|
||||
Handlebars.precompile = function(data, options) {
|
||||
it('should set data flag', function () {
|
||||
Handlebars.precompile = function (data, options) {
|
||||
equal(options.data, true);
|
||||
return 'simple';
|
||||
};
|
||||
@@ -214,45 +221,45 @@ describe('precompiler', function() {
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should set known helpers', function() {
|
||||
Handlebars.precompile = function(data, options) {
|
||||
it('should set known helpers', function () {
|
||||
Handlebars.precompile = function (data, options) {
|
||||
equal(options.knownHelpers.foo, true);
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output to file system', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output to file system', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
simple: true,
|
||||
output: 'file!'
|
||||
output: 'file!',
|
||||
});
|
||||
equal(file, 'file!');
|
||||
equal(content, 'simple\n');
|
||||
equal(log, '');
|
||||
});
|
||||
|
||||
it('should output minimized templates', function() {
|
||||
Handlebars.precompile = function() {
|
||||
it('should output minimized templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
uglify.minify = function() {
|
||||
uglify.minify = function () {
|
||||
return { code: 'min' };
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
equal(log, 'min');
|
||||
});
|
||||
|
||||
it('should omit minimization gracefully, if uglify-js is missing', function() {
|
||||
it('should omit minimization gracefully, if uglify-js is missing', function () {
|
||||
var error = new Error("Cannot find module 'uglify-js'");
|
||||
error.code = 'MODULE_NOT_FOUND';
|
||||
mockRequireUglify(error, function() {
|
||||
mockRequireUglify(error, function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function() {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
@@ -262,12 +269,12 @@ describe('precompiler', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail on errors (other than missing module) while loading uglify-js', function() {
|
||||
mockRequireUglify(new Error('Mock Error'), function() {
|
||||
it('should fail on errors (other than missing module) while loading uglify-js', function () {
|
||||
mockRequireUglify(new Error('Mock Error'), function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function() {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
@@ -278,35 +285,35 @@ describe('precompiler', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
it('should output map', function () {
|
||||
Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
it('should output map', function () {
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
min: true,
|
||||
map: 'foo.js.map'
|
||||
map: 'foo.js.map',
|
||||
});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
});
|
||||
|
||||
describe('#loadTemplates', function() {
|
||||
it('should throw on missing template', function(done) {
|
||||
Precompiler.loadTemplates({ files: ['foo'] }, function(err) {
|
||||
describe('#loadTemplates', function () {
|
||||
it('should throw on missing template', function (done) {
|
||||
Precompiler.loadTemplates({ files: ['foo'] }, function (err) {
|
||||
equal(err.message, 'Unable to open template file "foo"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should enumerate directories by extension', function(done) {
|
||||
it('should enumerate directories by extension', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
|
||||
function(err, opts) {
|
||||
function (err, opts) {
|
||||
equal(opts.templates.length, 2);
|
||||
equal(opts.templates[0].name, 'example_2');
|
||||
|
||||
@@ -314,10 +321,10 @@ describe('precompiler', function() {
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should enumerate all templates by extension', function(done) {
|
||||
it('should enumerate all templates by extension', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
|
||||
function(err, opts) {
|
||||
function (err, opts) {
|
||||
equal(opts.templates.length, 5);
|
||||
equal(opts.templates[0].name, 'bom');
|
||||
equal(opts.templates[1].name, 'empty');
|
||||
@@ -326,50 +333,50 @@ describe('precompiler', function() {
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should handle regular expression characters in extensions', function(done) {
|
||||
it('should handle regular expression characters in extensions', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
|
||||
function(err) {
|
||||
function (err) {
|
||||
// Success is not throwing
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should handle BOM', function(done) {
|
||||
it('should handle BOM', function (done) {
|
||||
var opts = {
|
||||
files: [__dirname + '/artifacts/bom.handlebars'],
|
||||
extension: 'handlebars',
|
||||
bom: true
|
||||
bom: true,
|
||||
};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].source, 'a');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle different root', function(done) {
|
||||
it('should handle different root', function (done) {
|
||||
var opts = {
|
||||
files: [__dirname + '/artifacts/empty.handlebars'],
|
||||
simple: true,
|
||||
root: 'foo/'
|
||||
root: 'foo/',
|
||||
};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept string inputs', function(done) {
|
||||
it('should accept string inputs', function (done) {
|
||||
var opts = { string: '' };
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, undefined);
|
||||
equal(opts.templates[0].source, '');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should accept string array inputs', function(done) {
|
||||
it('should accept string array inputs', function (done) {
|
||||
var opts = { string: ['', 'bar'], name: ['beep', 'boop'] };
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, 'beep');
|
||||
equal(opts.templates[0].source, '');
|
||||
equal(opts.templates[1].name, 'boop');
|
||||
@@ -377,9 +384,9 @@ describe('precompiler', function() {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should accept stdin input', function(done) {
|
||||
it('should accept stdin input', function (done) {
|
||||
var stdin = require('mock-stdin').stdin();
|
||||
Precompiler.loadTemplates({ string: '-' }, function(err, opts) {
|
||||
Precompiler.loadTemplates({ string: '-' }, function (err, opts) {
|
||||
equal(opts.templates[0].source, 'foo');
|
||||
done(err);
|
||||
});
|
||||
@@ -387,9 +394,9 @@ describe('precompiler', function() {
|
||||
stdin.send('o');
|
||||
stdin.end();
|
||||
});
|
||||
it('error on name missing', function(done) {
|
||||
it('error on name missing', function (done) {
|
||||
var opts = { string: ['', 'bar'] };
|
||||
Precompiler.loadTemplates(opts, function(err) {
|
||||
Precompiler.loadTemplates(opts, function (err) {
|
||||
equal(
|
||||
err.message,
|
||||
'Number of names did not match the number of string inputs'
|
||||
@@ -398,8 +405,8 @@ describe('precompiler', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete when no args are passed', function(done) {
|
||||
Precompiler.loadTemplates({}, function(err, opts) {
|
||||
it('should complete when no args are passed', function (done) {
|
||||
Precompiler.loadTemplates({}, function (err, opts) {
|
||||
equal(opts.templates.length, 0);
|
||||
done(err);
|
||||
});
|
||||
|
||||
+280
-304
@@ -1,64 +1,56 @@
|
||||
describe('Regressions', function() {
|
||||
it('GH-94: Cannot read property of undefined', function() {
|
||||
var data = {
|
||||
books: [
|
||||
{
|
||||
title: 'The origin of species',
|
||||
author: {
|
||||
name: 'Charles Darwin'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Lazarillo de Tormes'
|
||||
}
|
||||
]
|
||||
};
|
||||
var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
|
||||
shouldCompileTo(
|
||||
string,
|
||||
data,
|
||||
'The origin of speciesCharles DarwinLazarillo de Tormes',
|
||||
'Renders without an undefined property error'
|
||||
);
|
||||
describe('Regressions', function () {
|
||||
it('GH-94: Cannot read property of undefined', function () {
|
||||
expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}')
|
||||
.withInput({
|
||||
books: [
|
||||
{
|
||||
title: 'The origin of species',
|
||||
author: {
|
||||
name: 'Charles Darwin',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Lazarillo de Tormes',
|
||||
},
|
||||
],
|
||||
})
|
||||
.withMessage('Renders without an undefined property error')
|
||||
.toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes');
|
||||
});
|
||||
|
||||
it("GH-150: Inverted sections print when they shouldn't", function() {
|
||||
it("GH-150: Inverted sections print when they shouldn't", function () {
|
||||
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{},
|
||||
'not set :: ',
|
||||
"inverted sections run when property isn't present in context"
|
||||
);
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ set: undefined },
|
||||
'not set :: ',
|
||||
'inverted sections run when property is undefined'
|
||||
);
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ set: false },
|
||||
'not set :: ',
|
||||
'inverted sections run when property is false'
|
||||
);
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ set: true },
|
||||
' :: set',
|
||||
"inverted sections don't run when property is true"
|
||||
);
|
||||
expectTemplate(string)
|
||||
.withMessage(
|
||||
"inverted sections run when property isn't present in context"
|
||||
)
|
||||
.toCompileTo('not set :: ');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({ set: undefined })
|
||||
.withMessage('inverted sections run when property is undefined')
|
||||
.toCompileTo('not set :: ');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({ set: false })
|
||||
.withMessage('inverted sections run when property is false')
|
||||
.toCompileTo('not set :: ');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({ set: true })
|
||||
.withMessage("inverted sections don't run when property is true")
|
||||
.toCompileTo(' :: set');
|
||||
});
|
||||
|
||||
it('GH-158: Using array index twice, breaks the template', function() {
|
||||
var string = '{{arr.[0]}}, {{arr.[1]}}';
|
||||
var data = { arr: [1, 2] };
|
||||
|
||||
shouldCompileTo(string, data, '1, 2', 'it works as expected');
|
||||
it('GH-158: Using array index twice, breaks the template', function () {
|
||||
expectTemplate('{{arr.[0]}}, {{arr.[1]}}')
|
||||
.withInput({ arr: [1, 2] })
|
||||
.withMessage('it works as expected')
|
||||
.toCompileTo('1, 2');
|
||||
});
|
||||
|
||||
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
|
||||
it("bug reported by @fat where lambdas weren't being properly resolved", function () {
|
||||
var string =
|
||||
'<strong>This is a slightly more complicated {{thing}}.</strong>.\n' +
|
||||
'{{! Just ignore this business. }}\n' +
|
||||
@@ -73,18 +65,19 @@ describe('Regressions', function() {
|
||||
'\n' +
|
||||
'<small>Nothing to check out...</small>\n' +
|
||||
'{{/hasThings}}';
|
||||
|
||||
var data = {
|
||||
thing: function() {
|
||||
thing: function () {
|
||||
return 'blah';
|
||||
},
|
||||
things: [
|
||||
{ className: 'one', word: '@fat' },
|
||||
{ className: 'two', word: '@dhg' },
|
||||
{ className: 'three', word: '@sayrer' }
|
||||
{ className: 'three', word: '@sayrer' },
|
||||
],
|
||||
hasThings: function() {
|
||||
hasThings: function () {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var output =
|
||||
@@ -95,316 +88,299 @@ describe('Regressions', function() {
|
||||
'<li class=two>@dhg</li>\n' +
|
||||
'<li class=three>@sayrer</li>\n' +
|
||||
'</ul>.\n';
|
||||
shouldCompileTo(string, data, output);
|
||||
|
||||
expectTemplate(string).withInput(data).toCompileTo(output);
|
||||
});
|
||||
|
||||
it('GH-408: Multiple loops fail', function() {
|
||||
var context = [
|
||||
{ name: 'John Doe', location: { city: 'Chicago' } },
|
||||
{ name: 'Jane Doe', location: { city: 'New York' } }
|
||||
];
|
||||
|
||||
var template = CompilerContext.compile(
|
||||
it('GH-408: Multiple loops fail', function () {
|
||||
expectTemplate(
|
||||
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
|
||||
);
|
||||
|
||||
var result = template(context);
|
||||
equals(
|
||||
result,
|
||||
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
|
||||
'It should output multiple times'
|
||||
);
|
||||
)
|
||||
.withInput([
|
||||
{ name: 'John Doe', location: { city: 'Chicago' } },
|
||||
{ name: 'Jane Doe', location: { city: 'New York' } },
|
||||
])
|
||||
.withMessage('It should output multiple times')
|
||||
.toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe');
|
||||
});
|
||||
|
||||
it('GS-428: Nested if else rendering', function() {
|
||||
it('GS-428: Nested if else rendering', function () {
|
||||
var succeedingTemplate =
|
||||
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
|
||||
var failingTemplate =
|
||||
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
|
||||
|
||||
var helpers = {
|
||||
blk: function(block) {
|
||||
blk: function (block) {
|
||||
return block.fn('');
|
||||
},
|
||||
inverse: function(block) {
|
||||
inverse: function (block) {
|
||||
return block.inverse('');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
|
||||
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
|
||||
expectTemplate(succeedingTemplate)
|
||||
.withHelpers(helpers)
|
||||
.toCompileTo(' Expected ');
|
||||
|
||||
expectTemplate(failingTemplate)
|
||||
.withHelpers(helpers)
|
||||
.toCompileTo(' Expected ');
|
||||
});
|
||||
|
||||
it('GH-458: Scoped this identifier', function() {
|
||||
shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
|
||||
it('GH-458: Scoped this identifier', function () {
|
||||
expectTemplate('{{./foo}}').withInput({ foo: 'bar' }).toCompileTo('bar');
|
||||
});
|
||||
|
||||
it('GH-375: Unicode line terminators', function() {
|
||||
shouldCompileTo('\u2028', {}, '\u2028');
|
||||
it('GH-375: Unicode line terminators', function () {
|
||||
expectTemplate('\u2028').toCompileTo('\u2028');
|
||||
});
|
||||
|
||||
it('GH-534: Object prototype aliases', function() {
|
||||
it('GH-534: Object prototype aliases', function () {
|
||||
/* eslint-disable no-extend-native */
|
||||
Object.prototype[0xd834] = true;
|
||||
|
||||
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
|
||||
expectTemplate('{{foo}}').withInput({ foo: 'bar' }).toCompileTo('bar');
|
||||
|
||||
delete Object.prototype[0xd834];
|
||||
/* eslint-enable no-extend-native */
|
||||
});
|
||||
|
||||
it('GH-437: Matching escaping', function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{{a}}');
|
||||
}, Error);
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{a}}}');
|
||||
}, Error);
|
||||
it('GH-437: Matching escaping', function () {
|
||||
expectTemplate('{{{a}}').toThrow(Error, /Parse error on/);
|
||||
expectTemplate('{{a}}}').toThrow(Error, /Parse error on/);
|
||||
});
|
||||
|
||||
it('GH-676: Using array in escaping mustache fails', function() {
|
||||
var string = '{{arr}}';
|
||||
it('GH-676: Using array in escaping mustache fails', function () {
|
||||
var data = { arr: [1, 2] };
|
||||
|
||||
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
|
||||
expectTemplate('{{arr}}')
|
||||
.withInput(data)
|
||||
.withMessage('it works as expected')
|
||||
.toCompileTo(data.arr.toString());
|
||||
});
|
||||
|
||||
it('Mustache man page', function() {
|
||||
var string =
|
||||
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
|
||||
var data = {
|
||||
name: 'Chris',
|
||||
value: 10000,
|
||||
taxed_value: 10000 - 10000 * 0.4,
|
||||
in_ca: true
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
string,
|
||||
data,
|
||||
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
|
||||
'the hello world mustache example works'
|
||||
);
|
||||
it('Mustache man page', function () {
|
||||
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,
|
||||
})
|
||||
.withMessage('the hello world mustache example works')
|
||||
.toCompileTo(
|
||||
'Hello Chris. You have just won $10000! Well, $6000, after taxes.'
|
||||
);
|
||||
});
|
||||
|
||||
it('GH-731: zero context rendering', function() {
|
||||
shouldCompileTo(
|
||||
'{{#foo}} This is {{bar}} ~ {{/foo}}',
|
||||
{ foo: 0, bar: 'OK' },
|
||||
' This is ~ '
|
||||
);
|
||||
it('GH-731: zero context rendering', function () {
|
||||
expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}')
|
||||
.withInput({
|
||||
foo: 0,
|
||||
bar: 'OK',
|
||||
})
|
||||
.toCompileTo(' This is ~ ');
|
||||
});
|
||||
|
||||
it('GH-820: zero pathed rendering', function() {
|
||||
shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
|
||||
it('GH-820: zero pathed rendering', function () {
|
||||
expectTemplate('{{foo.bar}}').withInput({ foo: 0 }).toCompileTo('');
|
||||
});
|
||||
|
||||
it('GH-837: undefined values for helpers', function() {
|
||||
var helpers = {
|
||||
str: function(value) {
|
||||
return value + '';
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
|
||||
it('GH-837: undefined values for helpers', function () {
|
||||
expectTemplate('{{str bar.baz}}')
|
||||
.withHelpers({
|
||||
str: function (value) {
|
||||
return value + '';
|
||||
},
|
||||
})
|
||||
.toCompileTo('undefined');
|
||||
});
|
||||
|
||||
it('GH-926: Depths and de-dupe', function() {
|
||||
var context = {
|
||||
name: 'foo',
|
||||
data: [1],
|
||||
notData: [1]
|
||||
};
|
||||
|
||||
var template = CompilerContext.compile(
|
||||
it('GH-926: Depths and de-dupe', function () {
|
||||
expectTemplate(
|
||||
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
|
||||
);
|
||||
|
||||
var result = template(context);
|
||||
equals(result, 'foo');
|
||||
)
|
||||
.withInput({
|
||||
name: 'foo',
|
||||
data: [1],
|
||||
notData: [1],
|
||||
})
|
||||
.toCompileTo('foo');
|
||||
});
|
||||
|
||||
it('GH-1021: Each empty string key', function() {
|
||||
var data = {
|
||||
'': 'foo',
|
||||
name: 'Chris',
|
||||
value: 10000
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
'{{#each data}}Key: {{@key}}\n{{/each}}',
|
||||
{ data: data },
|
||||
'Key: \nKey: name\nKey: value\n'
|
||||
);
|
||||
it('GH-1021: Each empty string key', function () {
|
||||
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', function() {
|
||||
var root = '{{#wrap}}{{>partial}}{{/wrap}}';
|
||||
var partials = {
|
||||
partial: '{{#wrap}}<partial>{{/wrap}}'
|
||||
};
|
||||
var helpers = {
|
||||
wrap: function(options) {
|
||||
return new Handlebars.SafeString(options.fn());
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileToWithPartials(
|
||||
root,
|
||||
[{}, helpers, partials],
|
||||
true,
|
||||
'<partial>'
|
||||
);
|
||||
it('GH-1054: Should handle simple safe string responses', function () {
|
||||
expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}')
|
||||
.withHelpers({
|
||||
wrap: function (options) {
|
||||
return new Handlebars.SafeString(options.fn());
|
||||
},
|
||||
})
|
||||
.withPartials({
|
||||
partial: '{{#wrap}}<partial>{{/wrap}}',
|
||||
})
|
||||
.toCompileTo('<partial>');
|
||||
});
|
||||
|
||||
it('GH-1065: Sparse arrays', function() {
|
||||
it('GH-1065: Sparse arrays', function () {
|
||||
var array = [];
|
||||
array[1] = 'foo';
|
||||
array[3] = 'bar';
|
||||
shouldCompileTo(
|
||||
'{{#each array}}{{@index}}{{.}}{{/each}}',
|
||||
{ array: array },
|
||||
'1foo3bar'
|
||||
);
|
||||
expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}')
|
||||
.withInput({ array: array })
|
||||
.toCompileTo('1foo3bar');
|
||||
});
|
||||
|
||||
it('GH-1093: Undefined helper context', function() {
|
||||
var obj = { foo: undefined, bar: 'bat' };
|
||||
var helpers = {
|
||||
helper: function() {
|
||||
// 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 (var name in this) {
|
||||
if (Object.prototype.hasOwnProperty.call(this, name)) {
|
||||
return 'found';
|
||||
it('GH-1093: Undefined helper context', function () {
|
||||
expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}')
|
||||
.withInput({ obj: { foo: undefined, bar: 'bat' } })
|
||||
.withHelpers({
|
||||
helper: function () {
|
||||
// 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 (var 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';
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
|
||||
[{ obj: obj }, helpers],
|
||||
'notfoundbat'
|
||||
);
|
||||
// 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', function() {
|
||||
var string =
|
||||
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
|
||||
var partials = {
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout:
|
||||
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
|
||||
};
|
||||
shouldCompileToWithPartials(
|
||||
string,
|
||||
[{}, {}, partials],
|
||||
true,
|
||||
'doctypelayoutsubcontent'
|
||||
);
|
||||
});
|
||||
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
|
||||
var string = '{{#> layout}}{{/layout}}';
|
||||
var partials = {
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout:
|
||||
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
|
||||
};
|
||||
shouldCompileToWithPartials(
|
||||
string,
|
||||
[{}, {}, partials],
|
||||
true,
|
||||
'doctypelayoutsubcontent'
|
||||
);
|
||||
});
|
||||
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
|
||||
var string = '{{#> layout}}Outer{{/layout}}';
|
||||
var partials = {
|
||||
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
|
||||
inner: ''
|
||||
};
|
||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
|
||||
it('should support multiple levels of inline partials', function () {
|
||||
expectTemplate(
|
||||
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'
|
||||
)
|
||||
.withPartials({
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout:
|
||||
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}',
|
||||
})
|
||||
.toCompileTo('doctypelayoutsubcontent');
|
||||
});
|
||||
|
||||
it('GH-1135 : Context handling within each iteration', function() {
|
||||
var obj = { array: [1], name: 'John' };
|
||||
var helpers = {
|
||||
myif: function(conditional, options) {
|
||||
if (conditional) {
|
||||
return options.fn(this);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
it('GH-1089: should support failover content in multiple levels of inline partials', function () {
|
||||
expectTemplate('{{#> layout}}{{/layout}}')
|
||||
.withPartials({
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout:
|
||||
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}',
|
||||
})
|
||||
.toCompileTo('doctypelayoutsubcontent');
|
||||
});
|
||||
|
||||
shouldCompileTo(
|
||||
it('GH-1099: should support greater than 3 nested levels of inline partials', function () {
|
||||
expectTemplate('{{#> layout}}Outer{{/layout}}')
|
||||
.withPartials({
|
||||
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
|
||||
inner: '',
|
||||
})
|
||||
.toCompileTo('Outer');
|
||||
});
|
||||
|
||||
it('GH-1135 : Context handling within each iteration', function () {
|
||||
expectTemplate(
|
||||
'{{#each array}}\n' +
|
||||
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
|
||||
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
|
||||
'{{/each}}',
|
||||
[obj, helpers],
|
||||
' 1. IF: John--\n' + ' 2. MYIF: John==\n'
|
||||
);
|
||||
'{{/each}}'
|
||||
)
|
||||
.withInput({ array: [1], name: 'John' })
|
||||
.withHelpers({
|
||||
myif: function (conditional, options) {
|
||||
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', function() {
|
||||
var string =
|
||||
it('GH-1186: Support block params for existing programs', function () {
|
||||
expectTemplate(
|
||||
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
|
||||
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
|
||||
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
|
||||
|
||||
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
|
||||
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
|
||||
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'
|
||||
)
|
||||
.withInput({
|
||||
listOne: ['a'],
|
||||
listTwo: ['b'],
|
||||
})
|
||||
.withMessage('')
|
||||
.toCompileTo('ab');
|
||||
});
|
||||
|
||||
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
|
||||
var string =
|
||||
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
|
||||
shouldCompileTo(
|
||||
string,
|
||||
{ value: 'parent', list: [null, 'a'] },
|
||||
'parent=parent parent=parent ',
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
|
||||
var string = 'template {{>partial}} template';
|
||||
var partials = {
|
||||
partialWithBlock:
|
||||
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
|
||||
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
|
||||
it('should allow hash with protected array names', function () {
|
||||
var obj = { array: [1], name: 'John' };
|
||||
var helpers = {
|
||||
helpa: function (options) {
|
||||
return options.hash.length;
|
||||
},
|
||||
};
|
||||
shouldCompileToWithPartials(
|
||||
string,
|
||||
[{}, {}, partials],
|
||||
true,
|
||||
'template block partial block template'
|
||||
);
|
||||
|
||||
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
|
||||
});
|
||||
|
||||
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
|
||||
it('should compile and execute templates', function() {
|
||||
it('GH-1319: "unless" breaks when "each" value equals "null"', function () {
|
||||
expectTemplate(
|
||||
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'
|
||||
)
|
||||
.withInput({
|
||||
value: 'parent',
|
||||
list: [null, 'a'],
|
||||
})
|
||||
.withMessage('')
|
||||
.toCompileTo('parent=parent parent=parent ');
|
||||
});
|
||||
|
||||
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function () {
|
||||
expectTemplate('template {{>partial}} template')
|
||||
.withPartials({
|
||||
partialWithBlock:
|
||||
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
|
||||
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}',
|
||||
})
|
||||
.toCompileTo('template block partial block template');
|
||||
});
|
||||
|
||||
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function () {
|
||||
it('should compile and execute templates', function () {
|
||||
var newHandlebarsInstance = Handlebars.create();
|
||||
|
||||
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
|
||||
newHandlebarsInstance.registerHelper('loud', function(value) {
|
||||
newHandlebarsInstance.registerHelper('loud', function (value) {
|
||||
return value.toUpperCase();
|
||||
});
|
||||
var result = newHandlebarsInstance.templates['test.hbs']({
|
||||
name: 'yehuda'
|
||||
name: 'yehuda',
|
||||
});
|
||||
equals(result.trim(), 'YEHUDA');
|
||||
});
|
||||
|
||||
it('should call "helperMissing" if a helper is missing', function() {
|
||||
it('should call "helperMissing" if a helper is missing', function () {
|
||||
var newHandlebarsInstance = Handlebars.create();
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
|
||||
newHandlebarsInstance.templates['test.hbs']({});
|
||||
},
|
||||
@@ -413,7 +389,7 @@ describe('Regressions', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function() {
|
||||
it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function () {
|
||||
var newHandlebarsInstance = Handlebars.create();
|
||||
registerTemplate(
|
||||
newHandlebarsInstance,
|
||||
@@ -425,7 +401,7 @@ describe('Regressions', function() {
|
||||
expect(
|
||||
newHandlebarsInstance.templates['test.hbs']({
|
||||
property: 'a',
|
||||
test: { a: 'b' }
|
||||
test: { a: 'b' },
|
||||
})
|
||||
).to.equal('b');
|
||||
});
|
||||
@@ -439,7 +415,7 @@ describe('Regressions', function() {
|
||||
function compiledTemplateVersion7() {
|
||||
return {
|
||||
compiler: [7, '>= 4.0.0'],
|
||||
main: function(container, depth0, helpers, partials, data) {
|
||||
main: function (container, depth0, helpers, partials, data) {
|
||||
return (
|
||||
container.escapeExpression(
|
||||
(
|
||||
@@ -454,7 +430,7 @@ describe('Regressions', function() {
|
||||
) + '\n\n'
|
||||
);
|
||||
},
|
||||
useData: true
|
||||
useData: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -462,7 +438,7 @@ describe('Regressions', function() {
|
||||
// This is the compiled version of "{{lookup test property}}"
|
||||
return {
|
||||
compiler: [7, '>= 4.0.0'],
|
||||
main: function(container, depth0, helpers, partials, data) {
|
||||
main: function (container, depth0, helpers, partials, data) {
|
||||
return container.escapeExpression(
|
||||
helpers.lookup.call(
|
||||
depth0 != null ? depth0 : container.nullContext || {},
|
||||
@@ -471,45 +447,45 @@ describe('Regressions', function() {
|
||||
{
|
||||
name: 'lookup',
|
||||
hash: {},
|
||||
data: data
|
||||
data: data,
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
useData: true
|
||||
useData: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow hash with protected array names', function() {
|
||||
var obj = { array: [1], name: 'John' };
|
||||
var helpers = {
|
||||
helpa: function(options) {
|
||||
return options.hash.length;
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
|
||||
it('should allow hash with protected array names', function () {
|
||||
expectTemplate('{{helpa length="foo"}}')
|
||||
.withInput({ array: [1], name: 'John' })
|
||||
.withHelpers({
|
||||
helpa: function (options) {
|
||||
return options.hash.length;
|
||||
},
|
||||
})
|
||||
.toCompileTo('foo');
|
||||
});
|
||||
|
||||
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
|
||||
describe('GH-1598: Performance degradation for partials since v4.3.0', function () {
|
||||
// Do not run test for runs without compiler
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newHandlebarsInstance;
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
newHandlebarsInstance = Handlebars.create();
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('should only compile global partials once', function() {
|
||||
it('should only compile global partials once', function () {
|
||||
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
|
||||
newHandlebarsInstance.registerPartial({
|
||||
dude: 'I am a partial'
|
||||
dude: 'I am a partial',
|
||||
});
|
||||
var string = 'Dudes: {{> dude}} {{> dude}}';
|
||||
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
|
||||
@@ -519,8 +495,8 @@ describe('Regressions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", function() {
|
||||
it('should treat undefined helpers like non-existing helpers', function() {
|
||||
describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", function () {
|
||||
it('should treat undefined helpers like non-existing helpers', function () {
|
||||
expectTemplate('{{foo}}')
|
||||
.withHelper('foo', undefined)
|
||||
.withInput({ foo: 'bar' })
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
|
||||
describe('Require', function() {
|
||||
it('Load .handlebars files with require()', function() {
|
||||
describe('Require', function () {
|
||||
it('Load .handlebars files with require()', function () {
|
||||
var template = require('./artifacts/example_1');
|
||||
equal(template, require('./artifacts/example_1.handlebars'));
|
||||
|
||||
@@ -10,7 +10,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function() {
|
||||
it('Load .hbs files with require()', function () {
|
||||
var template = require('./artifacts/example_2');
|
||||
equal(template, require('./artifacts/example_2.hbs'));
|
||||
|
||||
|
||||
+15
-60
@@ -1,53 +1,53 @@
|
||||
describe('runtime', function() {
|
||||
describe('#template', function() {
|
||||
it('should throw on invalid templates', function() {
|
||||
describe('runtime', function () {
|
||||
describe('#template', function () {
|
||||
it('should throw on invalid templates', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template({});
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: object'
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template();
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: undefined'
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template('');
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: string'
|
||||
);
|
||||
});
|
||||
it('should throw on version mismatch', function() {
|
||||
it('should throw on version mismatch', function () {
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1]
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1],
|
||||
});
|
||||
},
|
||||
Error,
|
||||
/Template was precompiled with a newer version of Handlebars than the current runtime/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
|
||||
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1],
|
||||
});
|
||||
},
|
||||
Error,
|
||||
/Template was precompiled with an older version of Handlebars than the current runtime/
|
||||
);
|
||||
shouldThrow(
|
||||
function() {
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {}
|
||||
main: {},
|
||||
});
|
||||
},
|
||||
Error,
|
||||
@@ -56,57 +56,12 @@ describe('runtime', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#child', function() {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should throw for depthed methods without depths', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
template._setup({});
|
||||
template._setup({});
|
||||
template._child(1);
|
||||
},
|
||||
Error,
|
||||
'must pass parent depths'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw for block param methods without params', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
template._setup({});
|
||||
template._setup({});
|
||||
template._child(1);
|
||||
},
|
||||
Error,
|
||||
'must pass block params'
|
||||
);
|
||||
});
|
||||
it('should expose child template', function() {
|
||||
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
equal(template._child(1)(), 'bar');
|
||||
equal(template._child(1)(), 'bar');
|
||||
});
|
||||
it('should render depthed content', function() {
|
||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#noConflict', function() {
|
||||
describe('#noConflict', function () {
|
||||
if (!CompilerContext.browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should reset on no conflict', function() {
|
||||
it('should reset on no conflict', function () {
|
||||
var reset = Handlebars;
|
||||
Handlebars.noConflict();
|
||||
equal(Handlebars, 'no-conflict');
|
||||
|
||||
+124
-124
@@ -1,129 +1,105 @@
|
||||
describe('security issues', function() {
|
||||
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
|
||||
it('should not allow constructors to be accessed', function() {
|
||||
describe('security issues', function () {
|
||||
describe('GH-1495: Prevent Remote Code Execution via constructor', function () {
|
||||
it('should not allow constructors to be accessed', function () {
|
||||
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
|
||||
.withInput({})
|
||||
.toCompileTo('');
|
||||
|
||||
expectTemplate('{{constructor.name}}')
|
||||
.withInput({})
|
||||
.toCompileTo('');
|
||||
expectTemplate('{{constructor.name}}').withInput({}).toCompileTo('');
|
||||
});
|
||||
|
||||
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
|
||||
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function () {
|
||||
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
|
||||
.withInput({})
|
||||
.withHelper('list', function(element) {
|
||||
.withHelper('list', function (element) {
|
||||
return [element];
|
||||
})
|
||||
.toCompileTo('');
|
||||
});
|
||||
|
||||
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
|
||||
shouldCompileTo(
|
||||
'{{constructor.name}}',
|
||||
{
|
||||
constructor: {
|
||||
name: 'here we go'
|
||||
}
|
||||
},
|
||||
'here we go'
|
||||
);
|
||||
shouldCompileTo(
|
||||
'{{lookup (lookup this "constructor") "name"}}',
|
||||
{
|
||||
constructor: {
|
||||
name: 'here we go'
|
||||
}
|
||||
},
|
||||
'here we go'
|
||||
);
|
||||
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function () {
|
||||
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"', function() {
|
||||
shouldCompileTo(
|
||||
'{{lookup (lookup this "constructor") "name"}}',
|
||||
{
|
||||
constructor: {
|
||||
name: 'here we go'
|
||||
}
|
||||
},
|
||||
'here we go'
|
||||
);
|
||||
it('should allow the "constructor" property to be accessed if it is an "own property"', function () {
|
||||
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', function() {
|
||||
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function () {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
|
||||
it('should throw an exception when calling "{{helperMissing}}" ', function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile('{{helperMissing}}');
|
||||
template({});
|
||||
}, Error);
|
||||
describe('without the option "allowExplicitCallOfHelperMissing"', function () {
|
||||
it('should throw an exception when calling "{{helperMissing}}" ', function () {
|
||||
expectTemplate('{{helperMissing}}').toThrow(Error);
|
||||
});
|
||||
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile(
|
||||
'{{#helperMissing}}{{/helperMissing}}'
|
||||
);
|
||||
template({});
|
||||
}, Error);
|
||||
|
||||
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function () {
|
||||
expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error);
|
||||
});
|
||||
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
||||
|
||||
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function () {
|
||||
var functionCalls = [];
|
||||
expect(function() {
|
||||
expect(function () {
|
||||
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
||||
template({
|
||||
fn: function() {
|
||||
fn: function () {
|
||||
functionCalls.push('called');
|
||||
}
|
||||
},
|
||||
});
|
||||
}).to.throw(Error);
|
||||
expect(functionCalls.length).to.equal(0);
|
||||
});
|
||||
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile(
|
||||
'{{#blockHelperMissing .}}{{/blockHelperMissing}}'
|
||||
);
|
||||
template({
|
||||
fn: function() {
|
||||
|
||||
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
|
||||
expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}')
|
||||
.withInput({
|
||||
fn: function () {
|
||||
return 'functionInData';
|
||||
}
|
||||
});
|
||||
}, Error);
|
||||
},
|
||||
})
|
||||
.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with the option "allowCallsToHelperMissing" set to true', function() {
|
||||
it('should not throw an exception when calling "{{helperMissing}}" ', function() {
|
||||
describe('with the option "allowCallsToHelperMissing" set to true', function () {
|
||||
it('should not throw an exception when calling "{{helperMissing}}" ', function () {
|
||||
var template = Handlebars.compile('{{helperMissing}}');
|
||||
template({}, { allowCallsToHelperMissing: true });
|
||||
});
|
||||
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
||||
|
||||
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function () {
|
||||
var template = Handlebars.compile(
|
||||
'{{#helperMissing}}{{/helperMissing}}'
|
||||
);
|
||||
template({}, { allowCallsToHelperMissing: true });
|
||||
});
|
||||
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
||||
|
||||
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function () {
|
||||
var functionCalls = [];
|
||||
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
||||
template(
|
||||
{
|
||||
fn: function() {
|
||||
fn: function () {
|
||||
functionCalls.push('called');
|
||||
}
|
||||
},
|
||||
},
|
||||
{ allowCallsToHelperMissing: true }
|
||||
);
|
||||
equals(functionCalls.length, 1);
|
||||
});
|
||||
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
||||
|
||||
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
|
||||
var template = Handlebars.compile(
|
||||
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
|
||||
);
|
||||
@@ -132,8 +108,8 @@ describe('security issues', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GH-1563', function() {
|
||||
it('should not allow to access constructor after overriding via __defineGetter__', function() {
|
||||
describe('GH-1563', function () {
|
||||
it('should not allow to access constructor after overriding via __defineGetter__', function () {
|
||||
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
|
||||
return this.skip(); // Browser does not support this exploit anyway
|
||||
}
|
||||
@@ -149,7 +125,7 @@ describe('security issues', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GH-1595: dangerous properties', function() {
|
||||
describe('GH-1595: dangerous properties', function () {
|
||||
var templates = [
|
||||
'{{constructor}}',
|
||||
'{{__defineGetter__}}',
|
||||
@@ -160,53 +136,51 @@ describe('security issues', function() {
|
||||
'{{lookup this "__defineGetter__"}}',
|
||||
'{{lookup this "__defineSetter__"}}',
|
||||
'{{lookup this "__lookupGetter__"}}',
|
||||
'{{lookup this "__proto__"}}'
|
||||
'{{lookup this "__proto__"}}',
|
||||
];
|
||||
|
||||
templates.forEach(function(template) {
|
||||
describe('access should be denied to ' + template, function() {
|
||||
it('by default', function() {
|
||||
expectTemplate(template)
|
||||
.withInput({})
|
||||
.toCompileTo('');
|
||||
templates.forEach(function (template) {
|
||||
describe('access should be denied to ' + template, function () {
|
||||
it('by default', function () {
|
||||
expectTemplate(template).withInput({}).toCompileTo('');
|
||||
});
|
||||
it(' with proto-access enabled', function() {
|
||||
it(' with proto-access enabled', function () {
|
||||
expectTemplate(template)
|
||||
.withInput({})
|
||||
.withRuntimeOptions({
|
||||
allowProtoPropertiesByDefault: true,
|
||||
allowProtoMethodsByDefault: true
|
||||
allowProtoMethodsByDefault: true,
|
||||
})
|
||||
.toCompileTo('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('GH-1631: disallow access to prototype functions', function() {
|
||||
describe('GH-1631: disallow access to prototype functions', function () {
|
||||
function TestClass() {}
|
||||
|
||||
TestClass.prototype.aProperty = 'propertyValue';
|
||||
TestClass.prototype.aMethod = function() {
|
||||
TestClass.prototype.aMethod = function () {
|
||||
return 'returnValue';
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
handlebarsEnv.resetLoggedPropertyAccesses();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('control access to prototype methods via "allowedProtoMethods"', function() {
|
||||
describe('control access to prototype methods via "allowedProtoMethods"', function () {
|
||||
checkProtoMethodAccess({});
|
||||
|
||||
describe('in compat mode', function() {
|
||||
describe('in compat mode', function () {
|
||||
checkProtoMethodAccess({ compat: true });
|
||||
});
|
||||
|
||||
function checkProtoMethodAccess(compileOptions) {
|
||||
it('should be prohibited by default and log a warning', function() {
|
||||
it('should be prohibited by default and log a warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
@@ -218,7 +192,7 @@ describe('security issues', function() {
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
});
|
||||
|
||||
it('should only log the warning once', function() {
|
||||
it('should only log the warning once', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
@@ -235,7 +209,7 @@ describe('security issues', function() {
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
});
|
||||
|
||||
it('can be allowed, which disables the warning', function() {
|
||||
it('can be allowed, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
@@ -243,85 +217,89 @@ describe('security issues', function() {
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowedProtoMethods: {
|
||||
aMethod: true
|
||||
}
|
||||
aMethod: true,
|
||||
},
|
||||
})
|
||||
.toCompileTo('returnValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned on by default, which disables the warning', function() {
|
||||
it('can be turned on by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoMethodsByDefault: true
|
||||
allowProtoMethodsByDefault: true,
|
||||
})
|
||||
.toCompileTo('returnValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned off by default, which disables the warning', function() {
|
||||
it('can be turned off by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoMethodsByDefault: false
|
||||
allowProtoMethodsByDefault: false,
|
||||
})
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned off, if turned on by default', function() {
|
||||
it('can be turned off, if turned on by default', function () {
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowedProtoMethods: {
|
||||
aMethod: false
|
||||
}
|
||||
aMethod: false,
|
||||
},
|
||||
})
|
||||
.toCompileTo('');
|
||||
});
|
||||
}
|
||||
|
||||
it('should cause the recursive lookup by default (in "compat" mode)', function() {
|
||||
it('should cause the recursive lookup by default (in "compat" mode)', function () {
|
||||
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
||||
.withInput({ aString: ' abc ', trim: 'trim' })
|
||||
.withCompileOptions({ compat: true })
|
||||
.toCompileTo('trim');
|
||||
});
|
||||
|
||||
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() {
|
||||
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function () {
|
||||
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
|
||||
.withInput({ aString: ' abc ', trim: 'trim' })
|
||||
.withCompileOptions({ compat: true })
|
||||
.withRuntimeOptions({
|
||||
allowedProtoMethods: {
|
||||
trim: true
|
||||
}
|
||||
trim: true,
|
||||
},
|
||||
})
|
||||
.toCompileTo('abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() {
|
||||
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function () {
|
||||
checkProtoPropertyAccess({});
|
||||
|
||||
describe('in compat-mode', function() {
|
||||
describe('in compat-mode', function () {
|
||||
checkProtoPropertyAccess({ compat: true });
|
||||
});
|
||||
|
||||
describe('in strict-mode', function () {
|
||||
checkProtoPropertyAccess({ strict: true });
|
||||
});
|
||||
|
||||
function checkProtoPropertyAccess(compileOptions) {
|
||||
it('should be prohibited by default and log a warning', function() {
|
||||
it('should be prohibited by default and log a warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
@@ -333,21 +311,21 @@ describe('security issues', function() {
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
});
|
||||
|
||||
it('can be explicitly prohibited by default, which disables the warning', function() {
|
||||
it('can be explicitly prohibited by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoPropertiesByDefault: false
|
||||
allowProtoPropertiesByDefault: false,
|
||||
})
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned on, which disables the warning', function() {
|
||||
it('can be turned on, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
@@ -355,69 +333,91 @@ describe('security issues', function() {
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowedProtoProperties: {
|
||||
aProperty: true
|
||||
}
|
||||
aProperty: true,
|
||||
},
|
||||
})
|
||||
.toCompileTo('propertyValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned on by default, which disables the warning', function() {
|
||||
it('can be turned on by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoPropertiesByDefault: true
|
||||
allowProtoPropertiesByDefault: true,
|
||||
})
|
||||
.toCompileTo('propertyValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('can be turned off, if turned on by default', function() {
|
||||
it('can be turned off, if turned on by default', function () {
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.withRuntimeOptions({
|
||||
allowProtoPropertiesByDefault: true,
|
||||
allowedProtoProperties: {
|
||||
aProperty: false
|
||||
}
|
||||
aProperty: false,
|
||||
},
|
||||
})
|
||||
.toCompileTo('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() {
|
||||
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function () {
|
||||
beforeEach(function simulateRuntimeWithoutLookupProperty() {
|
||||
var oldTemplateMethod = handlebarsEnv.template;
|
||||
sinon.replace(handlebarsEnv, 'template', function(templateSpec) {
|
||||
sinon.replace(handlebarsEnv, 'template', function (templateSpec) {
|
||||
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
|
||||
return oldTemplateMethod.call(this, templateSpec);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('should work with simple properties', function() {
|
||||
it('should work with simple properties', function () {
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput({ aProperty: 'propertyValue' })
|
||||
.toCompileTo('propertyValue');
|
||||
});
|
||||
|
||||
it('should work with Array.prototype.length', function() {
|
||||
it('should work with Array.prototype.length', function () {
|
||||
expectTemplate('{{anArray.length}}')
|
||||
.withInput({ anArray: ['a', 'b', 'c'] })
|
||||
.toCompileTo('3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapes template variables', function () {
|
||||
it('in compat mode', function () {
|
||||
expectTemplate("{{'a\\b'}}")
|
||||
.withCompileOptions({ compat: true })
|
||||
.withInput({ 'a\\b': 'c' })
|
||||
.toCompileTo('c');
|
||||
});
|
||||
|
||||
it('in default mode', function () {
|
||||
expectTemplate("{{'a\\b'}}")
|
||||
.withCompileOptions()
|
||||
.withInput({ 'a\\b': 'c' })
|
||||
.toCompileTo('c');
|
||||
});
|
||||
it('in default mode', function () {
|
||||
expectTemplate("{{'a\\b'}}")
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ 'a\\b': 'c' })
|
||||
.toCompileTo('c');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wrapToAdjustContainer(precompiledTemplateFunction) {
|
||||
|
||||
+6
-6
@@ -7,26 +7,26 @@ try {
|
||||
/* NOP for in browser */
|
||||
}
|
||||
|
||||
describe('source-map', function() {
|
||||
describe('source-map', function () {
|
||||
if (!Handlebars.precompile || !SourceMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should safely include source map info', function() {
|
||||
it('should safely include source map info', function () {
|
||||
var template = Handlebars.precompile('{{hello}}', {
|
||||
destName: 'dest.js',
|
||||
srcName: 'src.hbs'
|
||||
srcName: 'src.hbs',
|
||||
});
|
||||
|
||||
equal(!!template.code, true);
|
||||
equal(!!template.map, !CompilerContext.browser);
|
||||
});
|
||||
it('should map source properly', function() {
|
||||
it('should map source properly', function () {
|
||||
var templateSource =
|
||||
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
|
||||
template = Handlebars.precompile(templateSource, {
|
||||
destName: 'dest.js',
|
||||
srcName: 'src.hbs'
|
||||
srcName: 'src.hbs',
|
||||
});
|
||||
|
||||
if (template.map) {
|
||||
@@ -49,7 +49,7 @@ function grepLine(token, lines) {
|
||||
if (column >= 0) {
|
||||
return {
|
||||
line: i + 1,
|
||||
column: column
|
||||
column: column,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+18
-34
@@ -1,61 +1,45 @@
|
||||
describe('spec', function() {
|
||||
describe('spec', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs');
|
||||
var fs = require('fs');
|
||||
|
||||
var specDir = __dirname + '/mustache/specs/';
|
||||
var specs = _.filter(fs.readdirSync(specDir), function(name) {
|
||||
return /.*\.json$/.test(name);
|
||||
});
|
||||
var specs = fs.readdirSync(specDir).filter((name) => /.*\.json$/.test(name));
|
||||
|
||||
_.each(specs, function(name) {
|
||||
specs.forEach(function (name) {
|
||||
var spec = require(specDir + name);
|
||||
_.each(spec.tests, function(test) {
|
||||
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec
|
||||
// We also do not support alternative delimeters
|
||||
spec.tests.forEach(function (test) {
|
||||
// Our lambda implementation knowingly deviates from the optional Mustache lambda spec
|
||||
// We also do not support alternative delimiters
|
||||
if (
|
||||
name === '~lambdas.json' ||
|
||||
// We also choose to throw if paritals are not found
|
||||
// We also choose to throw if partials are not found
|
||||
(name === 'partials.json' && test.name === 'Failed Lookup') ||
|
||||
// We nest the entire response from partials, not just the literals
|
||||
(name === 'partials.json' && test.name === 'Standalone Indentation') ||
|
||||
/\{\{=/.test(test.template) ||
|
||||
_.any(test.partials, function(partial) {
|
||||
return /\{\{=/.test(partial);
|
||||
})
|
||||
Object.values(test.partials || {}).some((value) => /\{\{=/.test(value))
|
||||
) {
|
||||
it.skip(name + ' - ' + test.name);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = _.clone(test.data);
|
||||
var data = Object.assign({}, test.data); // Shallow copy
|
||||
if (data.lambda) {
|
||||
// Blergh
|
||||
/* eslint-disable no-eval */
|
||||
/* eslint-disable-next-line no-eval */
|
||||
data.lambda = eval('(' + data.lambda.js + ')');
|
||||
/* eslint-enable no-eval */
|
||||
}
|
||||
it(name + ' - ' + test.name, function() {
|
||||
if (test.partials) {
|
||||
shouldCompileToWithPartials(
|
||||
test.template,
|
||||
[data, {}, test.partials, true],
|
||||
true,
|
||||
test.expected,
|
||||
test.desc + ' "' + test.template + '"'
|
||||
);
|
||||
} else {
|
||||
shouldCompileTo(
|
||||
test.template,
|
||||
[data, {}, {}, true],
|
||||
test.expected,
|
||||
test.desc + ' "' + test.template + '"'
|
||||
);
|
||||
}
|
||||
it(name + ' - ' + test.name, function () {
|
||||
expectTemplate(test.template)
|
||||
.withInput(data)
|
||||
.withPartials(test.partials || {})
|
||||
.withCompileOptions({ compat: true })
|
||||
.withMessage(test.desc + ' "' + test.template + '"')
|
||||
.toCompileTo(test.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+127
-193
@@ -1,169 +1,115 @@
|
||||
var Exception = Handlebars.Exception;
|
||||
|
||||
describe('strict', function() {
|
||||
describe('strict mode', function() {
|
||||
it('should error on missing property lookup', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{hello}}', { strict: true });
|
||||
|
||||
template({});
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
});
|
||||
it('should error on missing child', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
|
||||
equals(template({ hello: { bar: 'foo' } }), 'foo');
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
template({ hello: {} });
|
||||
},
|
||||
Exception,
|
||||
/"bar" not defined in/
|
||||
);
|
||||
});
|
||||
it('should handle explicit undefined', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
|
||||
|
||||
equals(template({ hello: { bar: undefined } }), '');
|
||||
});
|
||||
it('should error on missing property lookup in known helpers mode', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {
|
||||
strict: true,
|
||||
knownHelpersOnly: true
|
||||
});
|
||||
|
||||
template({});
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
});
|
||||
it('should error on missing context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', { strict: true });
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
describe('strict', function () {
|
||||
describe('strict mode', function () {
|
||||
it('should error on missing property lookup', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}', { strict: true });
|
||||
equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
|
||||
it('should error on missing child', function () {
|
||||
expectTemplate('{{hello.bar}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ hello: { bar: 'foo' } })
|
||||
.toCompileTo('foo');
|
||||
|
||||
shouldThrow(function() {
|
||||
template();
|
||||
}, Error);
|
||||
expectTemplate('{{hello.bar}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ hello: {} })
|
||||
.toThrow(Exception, /"bar" not defined in/);
|
||||
});
|
||||
|
||||
it('should not run helperMissing for helper calls', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{hello foo}}', {
|
||||
strict: true
|
||||
});
|
||||
|
||||
template({ foo: true });
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
|
||||
strict: true
|
||||
});
|
||||
|
||||
template({ foo: true });
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
});
|
||||
it('should throw on ambiguous blocks', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
|
||||
strict: true
|
||||
});
|
||||
|
||||
template({});
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
|
||||
strict: true
|
||||
});
|
||||
|
||||
template({});
|
||||
},
|
||||
Exception,
|
||||
/"hello" not defined in/
|
||||
);
|
||||
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#hello.bar}}{{/hello.bar}}',
|
||||
{ strict: true }
|
||||
);
|
||||
|
||||
template({ hello: {} });
|
||||
},
|
||||
Exception,
|
||||
/"bar" not defined in/
|
||||
);
|
||||
it('should handle explicit undefined', function () {
|
||||
expectTemplate('{{hello.bar}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ hello: { bar: undefined } })
|
||||
.toCompileTo('');
|
||||
});
|
||||
|
||||
it('should allow undefined parameters when passed to helpers', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#unless foo}}success{{/unless}}',
|
||||
{ strict: true }
|
||||
);
|
||||
equals(template({}), 'success');
|
||||
it('should error on missing property lookup in known helpers mode', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({
|
||||
strict: true,
|
||||
knownHelpersOnly: true,
|
||||
})
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
});
|
||||
|
||||
it('should allow undefined hash when passed to helpers', function() {
|
||||
var template = CompilerContext.compile('{{helper value=@foo}}', {
|
||||
strict: true
|
||||
it('should error on missing context', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toThrow(Error);
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function () {
|
||||
var xt = expectTemplate('{{@hello}}').withCompileOptions({
|
||||
strict: true,
|
||||
});
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
equals('value' in options.hash, true);
|
||||
equals(options.hash.value, undefined);
|
||||
return 'success';
|
||||
}
|
||||
};
|
||||
equals(template({}, { helpers: helpers }), 'success');
|
||||
|
||||
xt.toThrow(Error);
|
||||
|
||||
xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo');
|
||||
});
|
||||
|
||||
it('should show error location on missing property lookup', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
var template = CompilerContext.compile('\n\n\n {{hello}}', {
|
||||
strict: true
|
||||
});
|
||||
template({});
|
||||
},
|
||||
Exception,
|
||||
'"hello" not defined in [object Object] - 4:5'
|
||||
);
|
||||
it('should not run helperMissing for helper calls', function () {
|
||||
expectTemplate('{{hello foo}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ foo: true })
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
|
||||
expectTemplate('{{#hello foo}}{{/hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ foo: true })
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
});
|
||||
|
||||
it('should error contains correct location properties on missing property lookup', function() {
|
||||
it('should throw on ambiguous blocks', function () {
|
||||
expectTemplate('{{#hello}}{{/hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
|
||||
expectTemplate('{{^hello}}{{/hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toThrow(Exception, /"hello" not defined in/);
|
||||
|
||||
expectTemplate('{{#hello.bar}}{{/hello.bar}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.withInput({ hello: {} })
|
||||
.toThrow(Exception, /"bar" not defined in/);
|
||||
});
|
||||
|
||||
it('should allow undefined parameters when passed to helpers', function () {
|
||||
expectTemplate('{{#unless foo}}success{{/unless}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should allow undefined hash when passed to helpers', function () {
|
||||
expectTemplate('{{helper value=@foo}}')
|
||||
.withCompileOptions({
|
||||
strict: true,
|
||||
})
|
||||
.withHelpers({
|
||||
helper: function (options) {
|
||||
equals('value' in options.hash, true);
|
||||
equals(options.hash.value, undefined);
|
||||
return 'success';
|
||||
},
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should show error location on missing property lookup', function () {
|
||||
expectTemplate('\n\n\n {{hello}}')
|
||||
.withCompileOptions({ strict: true })
|
||||
.toThrow(Exception, '"hello" not defined in [object Object] - 4:5');
|
||||
});
|
||||
|
||||
it('should error contains correct location properties on missing property lookup', function () {
|
||||
try {
|
||||
var template = CompilerContext.compile('\n\n\n {{hello}}', {
|
||||
strict: true
|
||||
strict: true,
|
||||
});
|
||||
template({});
|
||||
} catch (error) {
|
||||
@@ -175,56 +121,44 @@ describe('strict', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('assume objects', function() {
|
||||
it('should ignore missing property', function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
|
||||
equal(template({}), '');
|
||||
});
|
||||
it('should ignore missing child', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
|
||||
equal(template({ hello: {} }), '');
|
||||
});
|
||||
it('should error on missing object', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
|
||||
template({});
|
||||
}, Error);
|
||||
});
|
||||
it('should error on missing context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
describe('assume objects', function () {
|
||||
it('should ignore missing property', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.toCompileTo('');
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{@hello.bar}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
it('should ignore missing child', function () {
|
||||
expectTemplate('{{hello.bar}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.withInput({ hello: {} })
|
||||
.toCompileTo('');
|
||||
});
|
||||
|
||||
it('should execute blockHelperMissing', function() {
|
||||
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
|
||||
assumeObjects: true
|
||||
});
|
||||
it('should error on missing object', function () {
|
||||
expectTemplate('{{hello.bar}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.toThrow(Error);
|
||||
});
|
||||
|
||||
equals(template({}), 'foo');
|
||||
it('should error on missing context', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.withInput(undefined)
|
||||
.toThrow(Error);
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function () {
|
||||
expectTemplate('{{@hello.bar}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.withInput(undefined)
|
||||
.toThrow(Error);
|
||||
});
|
||||
|
||||
it('should execute blockHelperMissing', function () {
|
||||
expectTemplate('{{^hello}}foo{{/hello}}')
|
||||
.withCompileOptions({ assumeObjects: true })
|
||||
.toCompileTo('foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
describe('string params mode', function() {
|
||||
it('arguments to helpers can be retrieved from options hash in string form', function() {
|
||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
|
||||
stringParams: true
|
||||
});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun) {
|
||||
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
|
||||
equals(
|
||||
result,
|
||||
'HELP ME MY BOSS is.a slave.driver',
|
||||
'String parameters output'
|
||||
);
|
||||
});
|
||||
|
||||
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#wycats is.a slave.driver}}help :({{/wycats}}',
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun, options) {
|
||||
return (
|
||||
'HELP ME MY BOSS ' +
|
||||
passiveVoice +
|
||||
' ' +
|
||||
noun +
|
||||
': ' +
|
||||
options.fn(this)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
|
||||
equals(
|
||||
result,
|
||||
'HELP ME MY BOSS is.a slave.driver: help :(',
|
||||
'String parameters output'
|
||||
);
|
||||
});
|
||||
|
||||
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return (
|
||||
'STOP ME FROM READING HACKER NEWS I ' +
|
||||
options.contexts[0][desire] +
|
||||
' ' +
|
||||
noun
|
||||
);
|
||||
},
|
||||
|
||||
with: function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{
|
||||
dale: {},
|
||||
|
||||
need: 'need-a'
|
||||
},
|
||||
{ helpers: helpers }
|
||||
);
|
||||
|
||||
equals(
|
||||
result,
|
||||
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
|
||||
'Proper context variable output'
|
||||
);
|
||||
});
|
||||
|
||||
it('information about the types is passed along', function() {
|
||||
var template = CompilerContext.compile(
|
||||
"{{tomdale 'need' dad.joke true false}}",
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
||||
equal(options.types[0], 'StringLiteral', 'the string type is passed');
|
||||
equal(
|
||||
options.types[1],
|
||||
'PathExpression',
|
||||
'the expression type is passed'
|
||||
);
|
||||
equal(
|
||||
options.types[2],
|
||||
'BooleanLiteral',
|
||||
'the expression type is passed'
|
||||
);
|
||||
equal(desire, 'need', 'the string form is passed for strings');
|
||||
equal(noun, 'dad.joke', 'the string form is passed for expressions');
|
||||
equal(trueBool, true, 'raw booleans are passed through');
|
||||
equal(falseBool, false, 'raw booleans are passed through');
|
||||
return 'Helper called';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it('hash parameters get type information', function() {
|
||||
var template = CompilerContext.compile(
|
||||
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, 'he.says');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
|
||||
equal(options.hashTypes.desire, 'StringLiteral');
|
||||
equal(options.hashTypes.noun, 'PathExpression');
|
||||
equal(options.hashTypes.bool, 'BooleanLiteral');
|
||||
equal(options.hash.desire, 'need');
|
||||
equal(options.hash.noun, 'dad.joke');
|
||||
equal(options.hash.bool, true);
|
||||
return 'Helper called';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it('hash parameters get context information', function() {
|
||||
var template = CompilerContext.compile(
|
||||
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var context = { dale: {} };
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, 'he.says');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
|
||||
equal(options.contexts.length, 1);
|
||||
equal(options.hashContexts.noun, context);
|
||||
equal(options.hash.desire, 'need');
|
||||
equal(options.hash.noun, 'dad.joke');
|
||||
equal(options.hash.bool, true);
|
||||
return 'Helper called';
|
||||
},
|
||||
with: function(withContext, options) {
|
||||
return options.fn(options.contexts[0][withContext]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(context, { helpers: helpers });
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return (
|
||||
'STOP ME FROM READING HACKER NEWS I ' +
|
||||
options.contexts[0][desire] +
|
||||
' ' +
|
||||
noun +
|
||||
' ' +
|
||||
options.fn(this)
|
||||
);
|
||||
},
|
||||
|
||||
with: function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{
|
||||
dale: {},
|
||||
|
||||
need: 'need-a'
|
||||
},
|
||||
{ helpers: helpers }
|
||||
);
|
||||
|
||||
equals(
|
||||
result,
|
||||
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
|
||||
'Proper context variable output'
|
||||
);
|
||||
});
|
||||
|
||||
it('with nested block ambiguous', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
with: function() {
|
||||
return 'WITH';
|
||||
},
|
||||
view: function() {
|
||||
return 'VIEW';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equals(result, 'WITH');
|
||||
});
|
||||
|
||||
it('should handle DATA', function() {
|
||||
var template = CompilerContext.compile('{{foo @bar}}', {
|
||||
stringParams: true
|
||||
});
|
||||
|
||||
var helpers = {
|
||||
foo: function(bar, options) {
|
||||
equal(bar, '@bar');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
return 'Foo!';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, 'Foo!');
|
||||
});
|
||||
});
|
||||
+178
-252
@@ -1,292 +1,218 @@
|
||||
describe('subexpressions', function() {
|
||||
it('arg-less helper', function() {
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val + val;
|
||||
},
|
||||
bar: function() {
|
||||
return 'LOL';
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
describe('subexpressions', function () {
|
||||
it('arg-less helper', function () {
|
||||
expectTemplate('{{foo (bar)}}!')
|
||||
.withHelpers({
|
||||
foo: function (val) {
|
||||
return val + val;
|
||||
},
|
||||
bar: function () {
|
||||
return 'LOL';
|
||||
},
|
||||
})
|
||||
.toCompileTo('LOLLOL!');
|
||||
});
|
||||
|
||||
it('helper w args', function() {
|
||||
var string = '{{blog (equal a b)}}';
|
||||
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
it('helper w args', function () {
|
||||
expectTemplate('{{blog (equal a b)}}')
|
||||
.withInput({ bar: 'LOL' })
|
||||
.withHelpers({
|
||||
blog: function (val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function (x, y) {
|
||||
return x === y;
|
||||
},
|
||||
})
|
||||
.toCompileTo('val is true');
|
||||
});
|
||||
|
||||
it('mixed paths and helpers', function() {
|
||||
var string = '{{blog baz.bat (equal a b) baz.bar}}';
|
||||
|
||||
var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
|
||||
var helpers = {
|
||||
blog: function(val, that, theOther) {
|
||||
return 'val is ' + val + ', ' + that + ' and ' + theOther;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!');
|
||||
it('mixed paths and helpers', function () {
|
||||
expectTemplate('{{blog baz.bat (equal a b) baz.bar}}')
|
||||
.withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } })
|
||||
.withHelpers({
|
||||
blog: function (val, that, theOther) {
|
||||
return 'val is ' + val + ', ' + that + ' and ' + theOther;
|
||||
},
|
||||
equal: function (x, y) {
|
||||
return x === y;
|
||||
},
|
||||
})
|
||||
.toCompileTo('val is foo!, true and bar!');
|
||||
});
|
||||
|
||||
it('supports much nesting', function() {
|
||||
var string = '{{blog (equal (equal true true) true)}}';
|
||||
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
it('supports much nesting', function () {
|
||||
expectTemplate('{{blog (equal (equal true true) true)}}')
|
||||
.withInput({ bar: 'LOL' })
|
||||
.withHelpers({
|
||||
blog: function (val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function (x, y) {
|
||||
return x === y;
|
||||
},
|
||||
})
|
||||
.toCompileTo('val is true');
|
||||
});
|
||||
|
||||
it('GH-800 : Complex subexpressions', function() {
|
||||
it('GH-800 : Complex subexpressions', function () {
|
||||
var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } };
|
||||
var helpers = {
|
||||
dash: function(a, b) {
|
||||
dash: function (a, b) {
|
||||
return a + '-' + b;
|
||||
},
|
||||
concat: function(a, b) {
|
||||
concat: function (a, b) {
|
||||
return a + b;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
"{{dash 'abc' (concat a b)}}",
|
||||
[context, helpers],
|
||||
'abc-ab'
|
||||
);
|
||||
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
|
||||
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
|
||||
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
|
||||
shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
|
||||
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', function() {
|
||||
var string = '{{equal (equal true true) true}}';
|
||||
|
||||
it('provides each nested helper invocation its own options hash', function () {
|
||||
var lastOptions = null;
|
||||
var helpers = {
|
||||
equal: function(x, y, options) {
|
||||
equal: function (x, y, options) {
|
||||
if (!options || options === lastOptions) {
|
||||
throw new Error('options hash was reused');
|
||||
}
|
||||
lastOptions = options;
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], 'true');
|
||||
});
|
||||
|
||||
it('with hashes', function() {
|
||||
var string = "{{blog (equal (equal true true) true fun='yes')}}";
|
||||
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
expectTemplate('{{equal (equal true true) true}}')
|
||||
.withHelpers(helpers)
|
||||
.toCompileTo('true');
|
||||
});
|
||||
|
||||
it('as hashes', function() {
|
||||
var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}";
|
||||
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
return 'val is ' + options.hash.fun;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], 'val is true');
|
||||
it('with hashes', function () {
|
||||
expectTemplate("{{blog (equal (equal true true) true fun='yes')}}")
|
||||
.withInput({ bar: 'LOL' })
|
||||
.withHelpers({
|
||||
blog: function (val) {
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function (x, y) {
|
||||
return x === y;
|
||||
},
|
||||
})
|
||||
.toCompileTo('val is true');
|
||||
});
|
||||
|
||||
it('multiple subexpressions in a hash', function() {
|
||||
var string =
|
||||
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
|
||||
|
||||
var helpers = {
|
||||
input: function(options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString(
|
||||
'<input aria-label="' +
|
||||
ariaLabel +
|
||||
'" placeholder="' +
|
||||
placeholder +
|
||||
'" />'
|
||||
);
|
||||
},
|
||||
t: function(defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[{}, helpers],
|
||||
'<input aria-label="Name" placeholder="Example User" />'
|
||||
);
|
||||
it('as hashes', function () {
|
||||
expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}")
|
||||
.withHelpers({
|
||||
blog: function (options) {
|
||||
return 'val is ' + options.hash.fun;
|
||||
},
|
||||
equal: function (x, y) {
|
||||
return x === y;
|
||||
},
|
||||
})
|
||||
.toCompileTo('val is true');
|
||||
});
|
||||
|
||||
it('multiple subexpressions in a hash with context', function() {
|
||||
var string =
|
||||
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
|
||||
|
||||
var context = {
|
||||
item: {
|
||||
field: 'Name',
|
||||
placeholder: 'Example User'
|
||||
}
|
||||
};
|
||||
|
||||
var helpers = {
|
||||
input: function(options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString(
|
||||
'<input aria-label="' +
|
||||
ariaLabel +
|
||||
'" placeholder="' +
|
||||
placeholder +
|
||||
'" />'
|
||||
);
|
||||
},
|
||||
t: function(defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
string,
|
||||
[context, helpers],
|
||||
'<input aria-label="Name" placeholder="Example User" />'
|
||||
);
|
||||
it('multiple subexpressions in a hash', function () {
|
||||
expectTemplate(
|
||||
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}'
|
||||
)
|
||||
.withHelpers({
|
||||
input: function (options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString(
|
||||
'<input aria-label="' +
|
||||
ariaLabel +
|
||||
'" placeholder="' +
|
||||
placeholder +
|
||||
'" />'
|
||||
);
|
||||
},
|
||||
t: function (defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
},
|
||||
})
|
||||
.toCompileTo('<input aria-label="Name" placeholder="Example User" />');
|
||||
});
|
||||
|
||||
it('in string params mode,', function() {
|
||||
var template = CompilerContext.compile(
|
||||
'{{snog (blorg foo x=y) yeah a=b}}',
|
||||
{ stringParams: true }
|
||||
);
|
||||
|
||||
var helpers = {
|
||||
snog: function(a, b, options) {
|
||||
equals(a, 'foo');
|
||||
equals(
|
||||
options.types.length,
|
||||
2,
|
||||
'string params for outer helper processed correctly'
|
||||
);
|
||||
equals(
|
||||
options.types[0],
|
||||
'SubExpression',
|
||||
'string params for outer helper processed correctly'
|
||||
);
|
||||
equals(
|
||||
options.types[1],
|
||||
'PathExpression',
|
||||
'string params for outer helper processed correctly'
|
||||
);
|
||||
return a + b;
|
||||
},
|
||||
|
||||
blorg: function(a, options) {
|
||||
equals(
|
||||
options.types.length,
|
||||
1,
|
||||
'string params for inner helper processed correctly'
|
||||
);
|
||||
equals(
|
||||
options.types[0],
|
||||
'PathExpression',
|
||||
'string params for inner helper processed correctly'
|
||||
);
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(
|
||||
{
|
||||
foo: {},
|
||||
yeah: {}
|
||||
},
|
||||
{ helpers: helpers }
|
||||
);
|
||||
|
||||
equals(result, 'fooyeah');
|
||||
it('multiple subexpressions in a hash with context', function () {
|
||||
expectTemplate(
|
||||
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}'
|
||||
)
|
||||
.withInput({
|
||||
item: {
|
||||
field: 'Name',
|
||||
placeholder: 'Example User',
|
||||
},
|
||||
})
|
||||
.withHelpers({
|
||||
input: function (options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString(
|
||||
'<input aria-label="' +
|
||||
ariaLabel +
|
||||
'" placeholder="' +
|
||||
placeholder +
|
||||
'" />'
|
||||
);
|
||||
},
|
||||
t: function (defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
},
|
||||
})
|
||||
.toCompileTo('<input aria-label="Name" placeholder="Example User" />');
|
||||
});
|
||||
|
||||
it('as hashes in string params mode', function() {
|
||||
var template = CompilerContext.compile('{{blog fun=(bork)}}', {
|
||||
stringParams: true
|
||||
});
|
||||
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
equals(options.hashTypes.fun, 'SubExpression');
|
||||
return 'val is ' + options.hash.fun;
|
||||
},
|
||||
bork: function() {
|
||||
return 'BORK';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equals(result, 'val is BORK');
|
||||
it('subexpression functions on the context', function () {
|
||||
expectTemplate('{{foo (bar)}}!')
|
||||
.withInput({
|
||||
bar: function () {
|
||||
return 'LOL';
|
||||
},
|
||||
})
|
||||
.withHelpers({
|
||||
foo: function (val) {
|
||||
return val + val;
|
||||
},
|
||||
})
|
||||
.toCompileTo('LOLLOL!');
|
||||
});
|
||||
|
||||
it('subexpression functions on the context', function() {
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {
|
||||
bar: function() {
|
||||
return 'LOL';
|
||||
}
|
||||
};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val + val;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
});
|
||||
|
||||
it("subexpressions can't just be property lookups", function() {
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {
|
||||
bar: 'LOL'
|
||||
};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val + val;
|
||||
}
|
||||
};
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
});
|
||||
it("subexpressions can't just be property lookups", function () {
|
||||
expectTemplate('{{foo (bar)}}!')
|
||||
.withInput({
|
||||
bar: 'LOL',
|
||||
})
|
||||
.withHelpers({
|
||||
foo: function (val) {
|
||||
return val + val;
|
||||
},
|
||||
})
|
||||
.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user