Compare commits

...

380 Commits

Author SHA1 Message Date
Jakob Linskeseder 88f789e419 Add possibility to precompile as ES module 2023-09-04 23:35:36 +02:00
Seyed Mohammad Mahdi Hatami 8ce2be4496 Improve error-message when partial is missing
Signed-off-by: Seyed Mohammad Mahdi Hatami <hatamik7@gmail.com>
2023-09-01 00:29:18 +02:00
Nils Knappmeier d3b93571a7 refactor: rename i to startPartIndex 2023-08-05 22:41:22 +02:00
Jakob Linskeseder 99d162cadc Fix link to homepage
When visiting the www-variant, a `SSL_ERROR_INTERNAL_ERROR_ALERT`
error is displayed by the browser.
2023-08-02 23:35:14 +02:00
Jakob Linskeseder 7147755b55 Allow manually triggering the release-action 2023-08-02 23:03:30 +02:00
Jakob Linskeseder 9413ea99ce Format *.md files with prettier
The `npm run format` command now matches
the husky `lint-staged`-commands.

Also upgraded to prettier v3.
2023-07-30 23:38:10 +02:00
Jakob Linskeseder f202c501a3 Pass correct environment for AWS S3 publishing
See #1972
2023-07-29 21:44:00 +02:00
Jakob Linskeseder bb55dead2d Checkout submodules when publishing to AWS S3
See #1972
2023-07-29 21:24:52 +02:00
Jakob Linskeseder 6e10b1f658 Fix publishing to AWS S3
See #1972
2023-07-29 21:18:15 +02:00
Jakob Linskeseder 65f24eb8b6 Publish latest and tags to AWS S3
This recreates the logic from this removed
TravisCI job:
https://github.com/handlebars-lang/handlebars.js/blob/a9a8e403213583ca90cb7c872d3a22796c37d961/.travis.yml#L16-L25
2023-07-29 21:09:35 +02:00
Jay Linski 35f001862c Create SECURITY.md
Closes #1961.
2023-07-25 19:21:46 +02:00
Jakob Linskeseder b3ffb16e5d Fix browser tests in CI pipeline 2023-07-24 21:55:20 +02:00
biodiscus 42adee3f82 Add the link to html-bundler-webpack-plugin in README 2023-07-24 21:53:06 +02:00
Jakob Linskeseder b4a54ad86e Test with Node 20 2023-05-17 01:30:18 +02:00
Jakob Linskeseder e6686963ba Use @handlebars/parser v2
We have to remove `--failAfterWarnings` from our Rollup integration-test,
because Typescript, when targeting es5, will use global `this` for transpilation.
Rollup warns about this, since it replaces `this` with `undefined`:

```
src/index.js → dist/bundle.js...
(!) `this` has been rewritten to `undefined`
https://rollupjs.org/guide/en/#error-this-is-undefined
../../../node_modules/@handlebars/parser/dist/esm/printer.js
1: var __spreadArrays = (this && this.__spreadArrays) || function () {}
```

See https://github.com/handlebars-lang/handlebars-parser/releases/tag/v2.0.0
2022-10-29 22:09:05 +02:00
Jakob Linskeseder 262b84b08a Fix integration-tests by not using browserlist from root
The `browserlist`-config was introduced in 6bc73fdad7.

See https://webpack.js.org/configuration/target/#browserslist.
2022-10-29 21:49:15 +02:00
Jakob Linskeseder 6186128ad0 Make library compatible with workers
Pulled from 4.x branch, see #1894.
2022-10-29 21:49:15 +02:00
Jakob Linskeseder 03fe783a69 Ignore prettier formatting-commit
See https://github.blog/changelog/2022-03-24-ignore-commits-in-the-blame-view-beta/
2022-10-29 21:49:15 +02:00
Jakob Linskeseder e534a911f3 Upgrade prettier to v2
Prettier v2 has the following breaking changes:
* enforces spaces between `function` and params
* enforces trailing commas by default
2022-10-29 21:49:15 +02:00
Jakob Linskeseder f6ff3bf52b Upgrade to ECMAScript 2020
This is needed in order to use `globalThis`, see #1894.
It also made it possible to remove some old polyfills and fallbacks.
2022-10-29 21:49:15 +02:00
Jakob Linskeseder da41887377 Get rid of underscore-dependency in specs 2022-10-29 21:49:15 +02:00
Jakob Linskeseder 83baaa469f Remove support for IE11 and dead browsers
Handlebars v5 will target modern browsers,
while older browsers will still receive support
via version 4.x.
2022-10-29 21:49:15 +02:00
Jakob Linskeseder 785a63e0a8 Test on Node 18 and drop EOL Node 10
* Updated lock-file to fix https://github.com/npm/cli/issues/4859.
* Updated integration-tests to webpack 5 to fix
  https://github.com/webpack/webpack/issues/14532.
* Added `mode` to webpack-integration-tests to avoid
  the warning `The 'mode' option has not been set...`.
* Replaced outdated `grunt-bg-shell`-package to get rid of
  coffee-script warnings
2022-10-16 22:21:56 +02:00
Jakob Linskeseder 903ca504b5 Remove unused parser tests
Parser tests are not used anymore, since the parser is
now living in a separate package.
2022-10-14 13:56:17 +02:00
1337617 69dbe65c19 Improving the README.markdown document 2022-09-17 19:00:38 +02:00
Nils Knappmeier 54cbc8c6c6 update package-lock.json 2022-06-21 22:26:22 +02:00
arturmakaranka b14ff4a6fc Fix compiling of each block params in strict mode
Fixes #1820
Closes #1821
2022-05-25 22:20:04 +02:00
Jakob Linskeseder e0de1d9a4a Add CI check for unmet dependencies
Fixes #1592
2022-05-20 22:37:11 +02:00
Jakob Linskeseder 9bff03fd3b Fix bundler issue with webpack 5
As explained in issue #1844 and in issue
https://github.com/webpack/webpack/issues/15007#issuecomment-996615250,
the way we used the `browser`-field was wrong.

The main reason for using the `browser`-field is the requirement of
`require('fs')` in the main-entry-file.
The workaround for this was using `require('handlebars/lib/handlebars')`,
but now it will also work via `require('handlebars')` for bundlers that
respect the `browser`-field.

The `"./runtime"`-config was removed, because it didn't have any effect.
In order to detect regressions, the webpack-integration test was
extended to test with different webpack versions.

Fixes #1174
Closes #1844
2022-05-17 20:46:30 +02:00
James Wilson a9fe6d5a03 use https instead of git for mustache submodule 2022-03-19 19:13:23 +01:00
Jakob Linskeseder ce8982fe55 Add Plop to in-the-wild list 2022-01-10 19:21:58 +01:00
Jakob Linskeseder 80809ef7fc Add more in-the-wild packages to README 2022-01-04 21:52:02 +01:00
Jakob Linskeseder 30bd78d457 Enable IDE code-highlighting for bin-file
By adding the `.js`-extension to the bin-file, IDEs will
know that js code-highlighting should be applied to the
file, which makes it easier for devs to read the code.
2022-01-02 19:19:00 +01:00
Jakob Linskeseder 0896d00c2e Remove outdated eco package from bench
The `eco`-templates (Embedded CoffeeScript templates)
had their last update over 10 years ago, so we can
remove this dependency from our benchmark.
2022-01-02 00:15:47 +01:00
Jakob Linskeseder f73be4ebf3 Remove unused jison package
This is package is not needed anymore since the parser
was moved to a standalone package in
19bdace85a.
2022-01-02 00:15:47 +01:00
Jakob Linskeseder 29ed243268 Replace shields.io with faster badgen.net
Badgen is a faster successor of shields.io:
https://github.com/badgen/badgen.net
2021-12-30 00:57:40 +01:00
Jakob Linskeseder dd6ff05079 Add npm version and stats to README 2021-12-30 00:47:47 +01:00
Jakob Linskeseder c18ed16f71 Update repository URL
Related to c295ef085f.
2021-12-30 00:47:47 +01:00
Jakob Linskeseder 7e5890cf5a Fix build-status badge in README 2021-12-30 00:08:26 +01:00
Jakob Linskeseder c7278d176a Fix security issue in y18n (CVE-2020-7774)
See https://github.com/advisories/GHSA-c4w7-xm78-47vh.
2021-12-30 00:04:27 +01:00
Jay Linski 35ea8e61bf Merge pull request #1774 from handlebars-lang/merge-4.x
Merge branch 4.x into master
2021-12-29 23:47:46 +01:00
Jakob Linskeseder a98b01c2ee Fix TypeScript linting
Also upgraded to `@definitelytyped/dtslint`.
2021-12-29 23:43:57 +01:00
Jakob Linskeseder b3b26712d9 Fix integration-tests issue with npm >= 7
Fixes the following error when running integration-tests:
```
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: webpack-test@1.0.0
npm ERR! Found: handlebars@5.0.0-alpha.1
npm ERR! node_modules/handlebars
npm ERR!   dev handlebars@"file:../../.." from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer handlebars@">= 1.3.0 < 5" from handlebars-loader@1.7.1
npm ERR! node_modules/handlebars-loader
npm ERR!   dev handlebars-loader@"^1.7.1" from the root project
npm ERR!
```
2021-12-29 23:29:47 +01:00
Jakob Linskeseder 37f4ea18b4 Remove AMD tests
AMD modules were removed in v5.
2021-12-29 23:21:14 +01:00
Jakob Linskeseder 48870b41f7 Update to lock-file version 2 2021-12-29 23:11:07 +01:00
Jakob Linskeseder 82c613244a Merge branch '4.x' into merge-4.x 2021-12-29 23:10:11 +01:00
Jakob Linskeseder e2f63da5c0 Don't create security alerts for dev-dependencies 2021-12-29 22:48:01 +01:00
Jakob Linskeseder 4da5882660 Remove non-standard package.json property
This was added in 88ee4757e7
and is not used anymore since a2687fdf50.
2021-12-29 21:48:34 +01:00
Jakob Linskeseder 78e7e28ff9 Replace custom test-server with Grunt connect 2021-12-29 21:35:16 +01:00
Jakob Linskeseder 03d387bf8e Fix rollup warning when importing Handlebars as ESM
Closes #1696
2021-12-29 21:35:16 +01:00
Jakob Linskeseder e0f50b4eec Add integration-tests to CI workflow 2021-12-29 21:35:16 +01:00
Jakob Linskeseder 9ed9418488 Replace Saucelabs with Playwright
Also reorganized npm scripts.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder ef0fc290b9 Move print-script.js-file in tests-folder
Also removed it from published files, since the script doesn't seem to be used.

Fourth part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder edc65b5c19 Move integration-testing-folder in tests-folder
Third part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder 715f4af179 Remove redundant task-part in task-tests-folder
Second part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder 3bd0fa8b32 Move bench-folder in tests-folder
First part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder 476d61667d Sort in-the-wild list by name
Also changed protocol to https.
2021-12-23 23:31:15 +01:00
Jakob Linskeseder 5ba0c72fe6 Add vite-package to in-the-wild list
Resolves https://github.com/handlebars-lang/docs/pull/70
2021-12-23 23:31:15 +01:00
Jakob Linskeseder 49621c15ac Remove archived projects from in-the-wild list 2021-12-23 23:31:15 +01:00
Jakob Linskeseder 1dc2c0bffb Link to new live demo (playground) on docs site
This site is served via HTTPS and provides the latest
Handlebars.js versions.
2021-12-23 23:31:15 +01:00
Jakob Linskeseder fe58756da2 Fix typos
This was found by running an IntellijIDE
code inspection for proofreading.
2021-12-22 20:36:26 +01:00
Jakob Linskeseder c295ef085f Update repository URL 2021-12-03 23:12:20 +01:00
Jakob Linskeseder c1ad3c8057 Update contributing guidelines 2021-12-03 23:12:20 +01:00
Jakob Linskeseder af92e32822 Run CI tests on more versions 2021-12-03 23:12:20 +01:00
Jakob Linskeseder 2954e7ea66 Don't rely on Node.js global object
If `global` is used and handlebars is compiled for browser
usage without a Node.js `global` polyfill, handlebars
fails with a `global is undefined` error.

Fixes #1593
2021-12-03 21:57:55 +01:00
Jakob Linskeseder b0f1a62ddb Add missing release for tag v4.2.2 2021-12-03 21:01:26 +01:00
Jakob Linskeseder 7d9450d3c3 Add missing release for tag v4.3.5
Also updated all URLs
2021-12-03 20:46:55 +01:00
Jay Linski 8eefee56ff Replace Travis CI with GitHub action (#1772) 2021-12-03 19:04:01 +01:00
Mehdi e538c29d92 Fix Grammar in "compiler-api" 2021-11-24 00:09:56 +01:00
Shon Frazier fd93073146 Fix Mustache links
...to point to 'https://mustache.github.io/...'
2021-11-23 23:58:57 +01:00
Nils Knappmeier 6790c080c6 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	integration-testing/multi-nodejs-test/test.sh
#	package-lock.json
#	package.json
2021-02-15 10:54:38 +01:00
Nils Knappmeier a9a8e40321 v4.7.7 2021-02-15 10:36:07 +01:00
Nils Knappmeier e66aed5b99 Update release notes 2021-02-15 10:35:33 +01:00
Nils Knappmeier 7d4d170ce4 disable IE in Saucelabs tests
The SauceLabs IE setup has problems and does not work
with the endpoints that we use to run them. This is a Saucelabs problem, but it has been there for a year now. Until we use another API, I will simply remove the browsers
2021-02-15 09:59:41 +01:00
Nils Knappmeier eb860c0899 fix weird error in integration tests
somehow the variable "$i" gets overwritten in integration-testing/multi-nodejs-test/test.sh:20, which is why we now rename it to a more meaningful variable
2021-02-15 09:50:16 +01:00
Pedro Lamas 2f509a8fca Add handlebars-action to Handlebars in the Wild (#1722) 2021-02-15 08:57:22 +01:00
Chris Garrett 19bdace85a [FEAT] Extract the Handlebars parser (#1713)
Extracts the parser to `@handlebars/parser`, where it can be shared
between different implementations. This means that e.g. Glimmer/Ember
will be able to iterate on new features without forcing Handlebars to
adopt them immediately, and vice versa. All implementors will be able to
absorb changes as it makes sense for them.
2021-02-15 08:56:25 +01:00
Nils Knappmeier b6d3de7123 fix: check prototype property access in strict-mode (#1736) 2021-02-13 13:41:40 +01:00
Nils Knappmeier f058970169 fix: escape property names in compat mode (#1736) 2021-02-13 13:40:53 +01:00
Nils Knappmeier 071003b89c docs: add note about reporting security-issues to contributing.md 2020-11-10 23:50:01 +01:00
Jay Linski 1a08e1d0a7 Update repository links
The project was moved, but some links weren't updated accordingly.
2020-08-12 08:42:19 +01:00
Nils Knappmeier 212f9d930b Merge branch '4.x'
# Conflicts:
#	integration-testing/multi-nodejs-test/test.sh
#	spec/string-params.js
#	spec/subexpressions.js
#	spec/track-ids.js
2020-05-05 00:08:00 +02:00
John Boehr 77825f8d35 refator: In spec tests, use expectTemplate over equals and shouldThrow (#1683) 2020-05-05 00:04:33 +02:00
Jordan Mele a65a55382c Moved uglify-js from optionalDependencies to peerDependencies 2020-04-24 22:09:12 +02:00
Tim Gates b1034a6702 docs: Fix simple typo, mulitple -> multiple
There is a small typo in lib/handlebars/compiler/whitespace-control.js.

Should read `multiple` rather than `mulitple`.
2020-04-15 23:04:50 +02:00
Hannah Wolfe 8727d2015a chore: bumping yargs to v15
fixes #1673

- we no longer need to pin to yargs 14 for node 6 & 8 support
2020-04-03 21:03:29 +01:00
Hannah Wolfe a39bfa5fe3 tests: added failing test for issue #1673
- demonstrates the regression
2020-04-03 21:03:29 +01:00
Hannah Wolfe 3789a30955 chore: start testing on Node.js 12 and 13
- make sure we're aware of all compatibility issues
2020-04-03 20:05:19 +01:00
Hannah Wolfe 75794c5dbc breaking: drop support for Node.js < v10
- handlebars is changing to a policy of only supporting current and active versions of Node.js
2020-04-03 20:03:37 +01:00
Hannah Wolfe 3438891bd2 Revert "Switch cmd parser to latest minimist"
This reverts commit c2f1e62031.
2020-04-03 19:26:21 +01:00
Hannah Wolfe 0ec71dcc5c Merge branch '4.x' 2020-04-03 19:24:50 +01:00
Hannah Wolfe e6ad93ea01 v4.7.6 2020-04-03 18:56:22 +01:00
Hannah Wolfe 2bf4fc6fd3 Update release notes 2020-04-03 18:55:39 +01:00
Hannah Wolfe b64202bc91 Update release-notes.md
- indicate changes have been reverted
2020-04-03 18:47:56 +01:00
Douglas Christopher Wilson c2f1e62031 Switch cmd parser to latest minimist 2020-04-03 18:44:30 +01:00
Hannah Wolfe 08e9a11a34 Revert "chore: set Node.js compatibility to v6+"
This reverts commit 309d2b49a1.
2020-04-03 18:44:30 +01:00
Hannah Wolfe 6f17a8e75c Merge branch '4.x' 2020-04-02 20:34:06 +01:00
Hannah Wolfe 1fd2edee2a v4.7.5 2020-04-02 20:09:49 +01:00
Hannah Wolfe 3c9c2f5cf2 Update release notes 2020-04-02 20:09:16 +01:00
Hannah Wolfe 16487a088e chore: downgrade yargs to v14
- allows us to support Node v6 and v8 for now
2020-04-02 19:28:36 +01:00
Hannah Wolfe 309d2b49a1 chore: set Node.js compatibility to v6+
- drop support for versions less than Node v6 via engines
- update integration tests to also test newer versions
2020-04-02 19:28:35 +01:00
Nils Knappmeier 645ac73844 test: fix integration tests
- They should fail, if any test fails
- Use "set -e" instead of "|| exit 1", because it suffices to be specified
  at the top of each file
2020-04-02 19:25:37 +01:00
Nils Knappmeier b454b02147 docs: update release-docs in CONTRIBUTING.md
- remove ember testing
- add docs for updating the website.
- add list of required accesses to publish
2020-04-02 14:10:47 +02:00
Hannah Wolfe f8eade7b41 Merge branch '4.x' 2020-04-01 18:54:36 +01:00
Hannah Wolfe d36adf9343 Revert "Update release-notes.md"
This reverts commit 2fd4a3da7a.
2020-04-01 18:49:13 +01:00
Hannah Wolfe 7adc19ab40 v4.7.4 2020-04-01 18:20:30 +01:00
Hannah Wolfe 9dd8d10e12 Update release notes 2020-04-01 18:19:34 +01:00
Hannah Wolfe 4671c4b383 Use tmp directory for files written during tests
- Add spec/tmp directory with .gitkeep file to indicate the folder is intentional
- Add the folder contents to .gitignore
- Use this folder to output the sourcemap file during bin tests. This file is a sideeffect of the main test
2020-04-01 18:04:14 +01:00
DeeDeeG e46baa1fe2 tasks/test-bin.js: Delete duplicate test
Per comment by @ErisDS on the GitHub PR:
https://github.com/wycats/handlebars.js/pull/1666#issuecomment-606805248
2020-04-01 17:25:35 +01:00
Hannah Wolfe c491b4ea25 Revert "Update release-notes.md"
This reverts commit 738391a064.
Think this needs to be left to the release tool :|
2020-04-01 17:21:20 +01:00
Hannah Wolfe 738391a064 Update release-notes.md 2020-03-31 17:05:33 +01:00
Hannah Wolfe 2fd4a3da7a Update release-notes.md 2020-03-31 17:04:46 +01:00
Aori Nevo 80c4516fda chore: add unit tests for cli options (#1666)
- adds full unit tests for all cli options
- demonstrates that nothing changes between minimist and yargs except a minor order change in the --help text
- proves b9c4b253e works the same as before

Co-authored-by: fabb <fabb@users.noreply.github.com>
2020-03-31 16:54:11 +01:00
Avi Vahl d79212a662 fix: migrate from optimist to yargs (#1666)
closes #1658

- adapted code from master to latest yargs (`.option` calls).

```
4.x:
found 188 vulnerabilities (169 low, 4 moderate, 14 high, 1 critical) in 5815 scanned packages

4.x with this PR:
found 32 vulnerabilities (17 low, 1 moderate, 13 high, 1 critical) in 5829 scanned packages
```
2020-03-31 16:54:11 +01:00
Avi Vahl b440c38886 chore: ignore external @types in tests
- some indirect dependencies install @types packages which are not compatible with the older typescript.
- adjusted test's tsconfig to not pick these up automatically, as the actual .d.ts does not depend on these external types.
2020-03-31 16:53:54 +01:00
Eli Skeggs 2dba7eee3f docs: fix comparison link 2020-03-05 15:10:10 +01:00
Ilja leyberman 3800b7dd4c Update README.markdown
added a new Project (openVALIDATION) to the section  Handlebars in the Wild
2020-02-25 22:15:55 +01:00
Nils Knappmeier 2ea32e8db9 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2020-02-05 06:13:36 +01:00
Nils Knappmeier c9789691af v4.7.3 2020-02-05 06:10:33 +01:00
Nils Knappmeier 9278f217e0 Update release notes 2020-02-05 06:10:09 +01:00
roydukkey d78cc73d3c Fixes spelling and punctuation
(cherry picked from commit fd3ca85d13)
2020-02-05 06:06:42 +01:00
ismailjones 4de51fe26b Add Type Definition for Handlebars.VERSION, Fixes #1647 2020-02-04 23:32:10 +01:00
papasmile a32d05f2fc Include Type Definition for runtime.js in Package 2020-02-04 21:58:49 +01:00
Nils Knappmeier ad63f5189f chore: add missing "await" in aws-s3 publishing code
closes #1644
2020-01-21 21:52:23 +01:00
roydukkey fd3ca85d13 Fixes spelling and punctuation 2020-01-13 22:00:27 +01:00
Nils Knappmeier edfe6b899c Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2020-01-13 21:58:50 +01:00
Nils Knappmeier 586e672c8b v4.7.2 2020-01-13 21:53:14 +01:00
Nils Knappmeier f0c6c4cc1f Update release notes 2020-01-13 21:52:50 +01:00
Nils Knappmeier a4fd391ba1 chore: execute saucelabs-task only if access-key exists
- up to now, the existance of the SAUCE_USERNAME was checked
  but this variable is even present in pull-requests from other
  repos. This means that builds fail, because the access key
  is not there.
  This change looks for SAUCE_ACCESS_KEY instead, which is
  a secure variable, only present in build originating from
  the handlebars.js repo.
2020-01-13 21:47:51 +01:00
Nils Knappmeier 9d5aa363cf fix: don't wrap helpers that are not functions
- helpers should always be a function, but in #1639 one seems to
  be undefined. This was not a problem before 4.6 because helpers
  weren't wrapped then.
  Now, we must take care only to wrap helpers (when adding
  the "lookupProperty" function to the options), if they
  are really functions.
2020-01-13 21:39:01 +01:00
Nils Knappmeier 14ba3d0c43 v4.7.1 2020-01-12 13:21:08 +01:00
Nils Knappmeier 4cddfe7017 Update release notes 2020-01-12 13:20:37 +01:00
Nils Knappmeier f152dfc892 fix: fix log output in case of illegal property access
- fix link url to handlebarsjs.com
2020-01-12 13:09:19 +01:00
Nils Knappmeier 3c1e252169 fix: log error for illegal property access only once per property 2020-01-12 13:06:56 +01:00
Nils Knappmeier 0d5c807017 v4.7.0 2020-01-10 17:24:06 +01:00
Nils Knappmeier 1f0834b1a2 Update release notes 2020-01-10 17:23:31 +01:00
Nils Knappmeier 575d8772e2 fix: use "logger" instead of console.error
... to be graceful with older browser without "console"
2020-01-10 17:06:57 +01:00
Nils Knappmeier 7af1c12db6 feat: default options for controlling proto access
This commmit adds the runtime options
- `allowProtoPropertiesByDefault` (boolean, default: false) and
- `allowProtoMethodsByDefault` (boolean, default: false)`
which can be used to allow access to prototype properties and
functions in general.

Specific properties and methods can still be disabled from access
via `allowedProtoProperties` and `allowedProtoMethods` by
setting the corresponding values to false.

The methods `constructor`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__`
and the property `__proto__` will be disabled, even if the allow...ByDefault-options
are set to true. In order to allow access to those properties and methods, they have
to be explicitly set to true in the 'allowedProto...'-options.

A warning is logged when the a proto-access it attempted and denied
by default (i.e. if no option is set by the user to make the access
decision explicit)
2020-01-10 16:55:45 +01:00
Nils Knappmeier 0c8230c253 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2020-01-09 00:17:28 +01:00
Nils Knappmeier 91a1b5d2f4 v4.6.0 2020-01-08 23:45:15 +01:00
Nils Knappmeier 770d746e60 Update release notes 2020-01-08 23:44:46 +01:00
Nils Knappmeier d7f0dcf2bb refactor: fix typo in private test method 2020-01-08 23:17:23 +01:00
Nils Knappmeier 187d611e8c test: add path to nodeJs when running test:bin
- this allows the test to be run in a debugger
  without the complete PATH
2020-01-08 23:17:23 +01:00
Nils Knappmeier d337f40d0e test: show diff when test:bin fails 2020-01-08 23:17:23 +01:00
Nils Knappmeier d03b6ecfc4 feat: access control to prototype properties via whitelist
Disallow access to prototype properties and methods by default.
Access to properties is always checked via
`Object.prototype.hasOwnProperty.call(parent, propertyName)`.

New runtime options:
- **allowedProtoMethods**: a string-to-boolean map of property-names that are allowed if they are methods of the parent object.
- **allowedProtoProperties**: a string-to-boolean map of property-names that are allowed if they are properties but not methods of the parent object.

```js
const template = handlebars.compile('{{aString.trim}}')
const result = template({ aString: '  abc  ' })
// result is empty, because trim is defined at String prototype
```

```js
const template = handlebars.compile('{{aString.trim}}')
const result = template({ aString: '  abc  ' }, {
  allowedProtoMethods: {
    trim: true
  }
})
// result = 'abc'
```

Implementation details: The method now "container.lookupProperty"
handles the prototype-checks and the white-lists. It is used in
- JavaScriptCompiler#nameLookup
- The "lookup"-helper (passed to all helpers as "options.lookupProperty")
- The "lookup" function at the container, which is used for recursive lookups in "compat" mode

Compatibility:
- **Old precompiled templates work with new runtimes**: The "options.lookupPropery"-function is passed to the helper by a wrapper, not by the compiled templated.
- **New templates work with old runtimes**: The template contains a function that is used as fallback if the "lookupProperty"-function cannot be found at the container. However, the runtime-options "allowedProtoProperties" and "allowedProtoMethods" only work with the newest runtime.

BREAKING CHANGE:
- access to prototype properties is forbidden completely by default
2020-01-08 23:17:23 +01:00
Nils Knappmeier d23ccf636a Merge branch '4.x'
# Conflicts:
#	Gruntfile.js
#	package-lock.json
#	package.json
2019-12-14 18:46:25 +01:00
Nils Knappmeier 164b7ff0de chore: ignore .nyc_output 2019-12-14 18:41:06 +01:00
Nils Knappmeier ac4655ee95 chore: disable "dot-notation" rule
- I don't think it makes much sense. In some cases,
  it is more readable to wrap property access in quotes,
  some times not, but there is no universal rule
  for that.
- a promises "catch"-function should not be wrapped, but it has to
  be, if "allow-keywords" is set to false
- an "if"-helper should be wrapped, but it is not allowed to be
  if "allow-keywords" is set to true (default).
2019-12-14 18:41:06 +01:00
Nils Knappmeier 14b621caf5 test/style: remove or hide unused code in git.js, add tests 2019-12-14 18:41:06 +01:00
Nils Knappmeier 1ec1737d24 test/style: refactor remaining grunt tasks to use promises instead of callbacks 2019-12-14 18:41:06 +01:00
Nils Knappmeier 1ebce2b53c test/style: use nyc instead of istanbul, npm audit fix 2019-12-14 18:41:06 +01:00
Nils Knappmeier 3a5b65e02b test/style: refactor parser task 2019-12-14 18:41:06 +01:00
Nils Knappmeier dde108e283 test/style: refactor test-task to make it more readable 2019-12-14 18:41:06 +01:00
Nils Knappmeier dc5495216d chore: change eslint-rules for tasks/
- use es2017 rules as NodeJS supports it today
- add "prefer-const"
2019-12-14 18:41:06 +01:00
Nils Knappmeier 8a062f19d5 Merge branch '4.x' 2019-12-08 16:41:29 +01:00
Marcos Marado d1fb07b32b Update (C) year in the LICENSE file
Welcome to 2019!
2019-12-08 16:21:38 +01:00
Nils Knappmeier 04b19848cc chore: try to fix saucelabs credentials (#1627) 2019-12-08 16:17:15 +01:00
Nils Knappmeier 44ee971e44 Merge branch '4.x' 2019-12-05 08:02:40 +01:00
Nils Knappmeier c40d9f33ad chore: active linting and formatting on commit 2019-12-05 08:01:33 +01:00
Nils Knappmeier 37411901da Merge branch '4.x-reformatted' into master-reformatted 2019-12-03 22:59:50 +01:00
Nils Knappmeier 8901c28e9a chore: fix task name in build 2019-12-03 22:59:04 +01:00
Nils Knappmeier dfe0fcb130 Merge branch '4.x'
# Conflicts:
#	Gruntfile.js
#	lib/handlebars/compiler/compiler.js
#	lib/handlebars/compiler/javascript-compiler.js
#	lib/handlebars/helpers/block-helper-missing.js
#	lib/handlebars/helpers/each.js
#	lib/handlebars/helpers/with.js
#	lib/handlebars/runtime.js
#	spec/compiler.js
#	spec/regressions.js
#	spec/runtime.js
#	spec/string-params.js
#	spec/subexpressions.js
#	spec/track-ids.js
2019-12-03 22:48:35 +01:00
Nils Knappmeier 3fb331ef40 style: reformat all files using prettier 2019-12-03 22:39:17 +01:00
Nils Knappmeier e97685e989 style: reformat all files using prettier 2019-12-03 22:37:15 +01:00
Nils Knappmeier 2f0991a022 update package-lock.json 2019-12-03 22:31:07 +01:00
Nils Knappmeier 4085e066fc Merge branch 'rebuild'
# Conflicts:
#	Gruntfile.js
#	package-lock.json
#	package.json
#	spec/amd-runtime.html
#	spec/amd.html
2019-12-03 22:28:41 +01:00
Nils Knappmeier e913dc5f12 chore: restructure build commands
- move dtslint (checkTypes) away from Gruntfile.js
  (as it disturbs debugging `grunt`)
  and call it directly as npm-script in travis-ci
- re-group task definition in Gruntfile.js
- use a multi-job travis build to
  run linting, format, dtslint and tests
  in parallel
- run only "npm test" on appveyor
- rename Grunt-tasks for be more descriptive
- SAUCE_USERNAME is not a secret variable anymore
  it can be easily guessed anyway and the
  [secure] value messes up with a lot of the
  log output

- linting on commit disable during transition
2019-12-03 22:21:30 +01:00
Nils Knappmeier 1f61f21250 chore: configure prettier and eslint
- add prettier to do formatting
- add eslint-config-prettier to
  disable rules conflicting with prettier
- remove eslint from grunt workflow
- use lint-stage to lint and format
  on precommit
- use eslint and prettier in travis directly
- remove rules that are already part of
  the "recommended" ruleset

That rational is that eslint and prettier should be run in
Travis-CI, on commit and as IDE integration (highlighting
errors directlry). They don't need to be run along with
test-cases. Getting linting errors when running the tests
because of missing semicolons is just annoying, but doesn't
help the overall code-quality.
2019-12-02 22:40:05 +01:00
Nils Knappmeier 587e7a3e63 remove yarn.lock 2019-12-02 22:40:04 +01:00
ole-martin edcc84f292 Update readme.md with updated links (#1620)
* Update readme.md with updated links

Fix http->https where possible and update links doc links

* Update README.markdown

* Fix precompilation link
2019-12-02 22:03:52 +01:00
ole-martin 23d58e79bb fix(runtime.js): partials compile not caching (#1600)
Reintroduce "merge" function, no called "mergeIfNeeded", that only creates a new partials
object if both "env.partials" and "options.partials" are set.

closes #1598
2019-11-18 21:21:03 +01:00
Nils Knappmeier dce6f54fdf Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package-lock.json
#	package.json
#	spec/amd-runtime.html
#	spec/amd.html
2019-11-18 08:26:56 +01:00
Nils Knappmeier c819c8b533 v4.5.3 2019-11-18 08:10:26 +01:00
Nils Knappmeier 827c9d0747 Update release notes 2019-11-18 08:09:48 +01:00
Nils Knappmeier f7f05d7558 fix: add "no-prototype-builtins" eslint-rule and fix all occurences 2019-11-18 07:33:45 +01:00
Nils Knappmeier 1988878087 fix: add more properties required to be enumerable
- __defineGetter__, __defineSetter__, __lookupGetter__, __proto__
2019-11-18 07:33:45 +01:00
Nils Knappmeier 886ba86c2f test/chore: add chai/expect and sinon to "runtime"-environment 2019-11-17 22:18:20 +01:00
Nils Knappmeier 0817dad7e7 test: add sinon as global variable to eslint in the specs 2019-11-15 17:22:29 +01:00
Nils Knappmeier 93516a0b07 test: add sinon.js for spies, deprecate current assertions
- the goal is to get overall cleaner test.
- chai and sinon are standard libraries for testing
  assertions and spies. We should not use custom ones.
2019-11-15 16:38:28 +01:00
Nils Knappmeier 93e284ed9b chore: add chai and dirty-chai for better test assertions 2019-11-15 16:38:28 +01:00
Nils Knappmeier c02b05fa81 fix: use !== 0 instead of != 0 2019-11-15 16:38:28 +01:00
Nils Knappmeier 7b11f55c53 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2019-11-14 08:10:05 +01:00
Nils Knappmeier 8de121d21c v4.5.2 2019-11-13 22:07:27 +01:00
Nils Knappmeier 6914090086 Update release notes 2019-11-13 22:06:51 +01:00
Nils Knappmeier d54137810a fix: use String(field) in lookup when checking for "constructor"
closes #1603
2019-11-13 22:02:05 +01:00
Nils Knappmeier c2ac79c970 test: add fluent API for testing Handlebars
use "expectTemplate(template)....toCompileTo(output)"
2019-11-13 22:02:05 +01:00
Nils Knappmeier b0bb2a42b4 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package-lock.json
#	package.json
2019-11-09 06:47:10 +01:00
Nils Knappmeier 7ef86173ab v4.5.1 2019-10-29 05:42:23 +01:00
Nils Knappmeier b75e3e1f40 Update release notes 2019-10-29 05:41:43 +01:00
Nils Knappmeier 5e9d17f8fa fix: move "eslint-plugin-compat" to devDependencies 2019-10-29 05:17:03 +01:00
Nils Knappmeier b24797da01 v4.5.0 2019-10-28 19:47:51 +01:00
Nils Knappmeier a243067883 Update release notes 2019-10-28 19:47:08 +01:00
Nils Knappmeier 088e61812a chore: add eslint-plugin-compat and eslint-plugin-es5
- tests are not compiled with babel and must thus be in
  es5
- we don't use polyfills, so we need to make sure no
  functions aren't used that are not supported by popular browsers. (like Object.assign in Safari and IE11)
- Boths are errors that usually only appear when running
  tests in SauceLabs, which happens only on _after_
  merging a PR.
2019-10-28 19:17:57 +01:00
Kabir Baidhya 7052e88068 Resolve deprecation warning message from eslint while running eslint (#1586)
* Resolve eslint deprecation warning

  There was a DeprecationWarning message from eslint saying that `ecmaFeatures` property has 
  been deprecated.
  Moved it under the `parserOptions` as per recommended here - 
  https://eslint.org/docs/user-guide/migrating-to-2.0.0.
* Set escmaVersion = 6
* Use ES6 built-in global variables
* Remove flags in favor of ecmaVersion
2019-10-28 18:32:49 +01:00
Kabir Baidhya b8913fcc65 Add missing types for the Exception class properties (#1583)
* Convert Exception to a class
* Add node param type declaration
* Test the types
2019-10-28 15:58:59 +01:00
Robert Jackson 62ed3c25c7 Add Handlebars.parseWithoutProcessing (#1584)
When authoring tooling that parses Handlebars files and emits Handlebars
files, you often want to preserve the **exact** formatting of the input.
The changes in this commit add a new method to the `Handlebars`
namespace: `parseWithoutProcessing`. Unlike, `Handlebars.parse` (which
will mutate the parsed AST to apply whitespace control) this method will
parse the template and return it directly (**without** processing
😉).

For example, parsing the following template:

```hbs
 {{#foo}}
   {{~bar~}} {{baz~}}
 {{/foo}}
```

Using `Handlebars.parse`, the AST returned would have truncated the
following whitespace:

* The whitespace prior to the `{{#foo}}`
* The newline following `{{#foo}}`
* The leading whitespace before `{{~bar~}}`
* The whitespace between `{{~bar~}}` and `{{baz~}}`
* The newline after `{{baz~}}`
* The whitespace prior to the `{{/foo}}`

When `Handlebars.parse` is used from  `Handlebars.precompile` or
`Handlebars.compile`, this whitespace stripping is **very** important
(these behaviors are intentional, and generally lead to better rendered
output).

When the same template is parsed with
`Handlebars.parseWithoutProcessing` none of those modifications to the
AST are made. This enables "codemod tooling" (e.g. `prettier` and
`ember-template-recast`) to preserve the **exact** initial formatting.
Prior to these changes, those tools would have to _manually_ reconstruct
the whitespace that is lost prior to emitting source.
2019-10-28 00:28:28 +01:00
Nils Knappmeier e46c239b3d Merge branch '4.x'
# Conflicts:
#	lib/handlebars/compiler/javascript-compiler.js
#	lib/handlebars/helpers/with.js
2019-10-27 17:31:48 +01:00
kpdecker 7fcf9d24f8 Use objects for hash value tracking
The use of arrays was incorrect for the data type and causing problems when hash keys conflicted with array behaviors.

Fixes #1194

(cherry picked from commit 768ddbd661)
2019-10-27 17:27:37 +01:00
Nils Knappmeier 081e1d0ad9 Merge branch '4.x'
# Conflicts:
#	lib/handlebars/compiler/javascript-compiler.js
2019-10-27 17:26:57 +01:00
kpdecker 2ef6fbd1fc Use objects for hash value tracking
The use of arrays was incorrect for the data type and causing problems when hash keys conflicted with array behaviors.

Fixes #1194

(cherry picked from commit 768ddbd661)
2019-10-27 17:25:40 +01:00
Hannah Wolfe c76ded8f0f fix: add guard to if & unless helpers (#1549)
fixes #1548

- add a guard to show readable syntax error for if / unless helper
- prevents against 3 different errors that can be generated by different systax errors
2019-10-27 15:41:35 +01:00
Nils Knappmeier 50e844b563 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2019-10-22 00:02:19 +02:00
Nils Knappmeier a15d01d383 Merge branch '4.4.x' into 4.x 2019-10-22 00:00:18 +02:00
Nils Knappmeier 8e1cce7918 v4.4.5 2019-10-20 23:08:10 +02:00
Nils Knappmeier def79fd5d6 Update release notes 2019-10-20 23:07:22 +02:00
Nils Knappmeier fd2e5c983d fix: non-eager matching raw-block-contents
In 4.4.4 the block-contents was matched with an eager match, which means
that with multiple raw-blocks of the same kind, the block was spanned
over the first ending-tag until the last one.
This commit replaces this by a non-eager match.

closes #1579
2019-10-20 22:57:13 +02:00
Nils Knappmeier 8d5530ee2c fix: non-eager matching raw-block-contents
In 4.4.4 the block-contents was matched with an eager match, which means
that with multiple raw-blocks of the same kind, the block was spanned
over the first ending-tag until the last one.
This commit replaces this by a non-eager match.

closes #1579
2019-10-20 22:56:24 +02:00
Nils Knappmeier 2ab261eab7 v4.4.4 2019-10-20 21:33:00 +02:00
Nils Knappmeier 143ac806da Update release notes 2019-10-20 21:32:09 +02:00
Nils Knappmeier f1752fe66f fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:24:55 +02:00
Nils Knappmeier aaab6099f2 fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:23:02 +02:00
Nils Knappmeier c56995d706 deprecate decorators 2019-10-09 07:16:20 +02:00
sohibe feb60f85c9 show source location for the strict lookup exceptions 2019-10-09 06:50:04 +02:00
Nils Knappmeier 566536b03d Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package-lock.json
#	package.json
2019-10-08 22:22:01 +02:00
Nils Knappmeier 0b593bfe5d chore: link to s3 bucket with https, add "npm ci" to build instructions 2019-10-08 22:19:01 +02:00
Nils Knappmeier 2e53fba68f v4.4.3 2019-10-08 22:05:35 +02:00
Nils Knappmeier ba570c42cf Update release notes 2019-10-08 22:04:57 +02:00
Nils Knappmeier 6aaac4a779 add text about "possibly breaking" label 2019-10-08 21:50:18 +02:00
Alexander Schramm 0440af2147 added missing type fields in typings and tests for them 2019-10-08 19:44:06 +02:00
Vladimir Kapustin 4ae86f3282 Add handlebars-wax to Handlebars in the Wild 2019-10-05 07:57:32 +02:00
Nils Knappmeier b793350fec v4.4.2 2019-10-02 22:44:36 +02:00
Nils Knappmeier 26d0f7a80d Update release notes 2019-10-02 22:44:03 +02:00
Nils Knappmeier b7eada0149 chore: fix grunt-saucelabs dependency 2019-10-02 22:42:52 +02:00
Nils Knappmeier b8e769fcb6 v4.4.1 2019-10-02 21:52:34 +02:00
Nils Knappmeier 9cb31653a4 Update release notes 2019-10-02 21:51:56 +02:00
Nils Knappmeier c53f340374 chore: use @knappi/grunt-saucelabs instead of github-dependency
- mostly because installing dependencies from github takes really
  long in this case.
2019-10-02 21:46:16 +02:00
Nils Knappmeier 059b330579 v4.4.0 2019-09-29 15:29:13 +02:00
Nils Knappmeier 8f6047cdfd Update release notes 2019-09-29 15:28:36 +02:00
antelle cf7545ef5a Added support for iterable objects in {{#each}} helper (#1557)
* Added support for iterable object in {{#each}} helper
Currently {{#each}} helper supports either arrays, or objects,
however nowadays you can define custom iterable objects by overriding
a special method called Symbol.iterator, which results in empty result
being rendered.
* improved a test for iterables in {{#each}} returning empty result
* #each helper: using ES5 instead of generator functions in tests
* #each helper: using ES5 in the helper itself
2019-09-29 14:57:47 +02:00
Nils Knappmeier c958cc8955 v4.3.4 2019-09-28 13:25:05 +02:00
Nils Knappmeier b250b2d53a Update release notes 2019-09-28 13:24:18 +02:00
Nils Knappmeier ff4d827c09 fix: harden "propertyIsEnumerable"-check
- "container" is an internal object that is most likely
  not accessible through templateing (unlike the proto of "Object", which might be.)
  In order to prevent overriding this method, we
  use "propertyIsEnumerable" from the constructor.
2019-09-28 10:36:49 +02:00
Nils Knappmeier e4738491b3 v4.3.3 2019-09-27 07:46:55 +02:00
Nils Knappmeier 54f7e11b28 Update release notes 2019-09-27 07:46:12 +02:00
Nils Knappmeier 8742bde701 fix test case for browsers that do not support __defineGetter__ 2019-09-27 00:09:04 +02:00
Nils Knappmeier 2357140c68 v4.3.2 2019-09-26 23:58:48 +02:00
Nils Knappmeier c5cbeac039 Update release notes 2019-09-26 23:58:13 +02:00
Nils Knappmeier 213c0bbe3c Use Object.prototype.propertyIsEnumerable to check for constructors
- context.propertyIsEnumerable can be replaced
  via __definedGetter__
- This is a fix specific to counter a known RCE exploit.
  Other fixes will follow.

closes #1563
2019-09-26 23:55:24 +02:00
Nils Knappmeier 050cca0866 v4.3.1 2019-09-25 00:32:32 +02:00
Nils Knappmeier 2d5579078b Update release notes 2019-09-25 00:31:48 +02:00
Nils Knappmeier e3639e2407 fix saucelabs tests (internet explorer) 2019-09-25 00:18:02 +02:00
Nils Knappmeier c2a17c7b30 fix saucelabs tests (internet explorer) 2019-09-25 00:00:51 +02:00
Nils Knappmeier 1266838829 do not break on precompiled templates from Handlebars >=4.0.0 <4.3.0
- The version-range above have compiler version 7 and
  precompiled templates expecte the (block)
  HelperMissing-functions in "helpers" and not in "container.hooks".
- Handlebars now accepts precompiled templates of version 7.
- If a precompiled template with version 7 is loaded,
  the (block)HelperMissing-functions are kept in "helpers"
2019-09-24 23:45:09 +02:00
Nils Knappmeier 64ecb9ea84 add test for #1560 2019-09-24 22:28:52 +02:00
Matt Hinchliffe 93444c58db Ensure allowCallsToHelperMissing runtime option is optional in typings 2019-09-24 22:27:16 +02:00
Nils Knappmeier 25bae31578 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	lib/handlebars/runtime.js
#	package.json
2019-09-24 08:56:55 +02:00
Nils Knappmeier d148d7cb1f check "allowCallsToHelperMissing" 2019-09-24 08:37:37 +02:00
Nils Knappmeier 74795a3a7f update jsfiddle 2019-09-24 08:29:11 +02:00
Nils Knappmeier a89081d440 v4.3.0 2019-09-24 08:04:35 +02:00
Nils Knappmeier bba65605b1 Update release notes 2019-09-24 08:03:52 +02:00
Nils Knappmeier 06b7224ed9 adjust compiler revision 2019-09-24 07:31:19 +02:00
Nils Knappmeier 2078c727c6 Disallow calling "helperMissing" and "blockHelperMissing" directly
closes #1558
2019-09-24 07:31:19 +02:00
Nils Knappmeier fff3e40402 v4.2.1 2019-09-20 19:40:48 +02:00
Nils Knappmeier 35bcd4405f Update release notes 2019-09-20 19:40:10 +02:00
Nils Knappmeier 0419daec52 fix accidental typo 2019-09-20 19:08:19 +02:00
king6cong 8df74a0119 FAQ format adjustment 2019-09-20 19:08:19 +02:00
Caleb Mazalevskis 00b4f2ff98 Fix some small typos. 2019-09-20 19:01:03 +02:00
Nils Knappmeier e47b5ec192 integration-tests for various webpack-scenarios
related to #1553

- registering helpers on an instance retrieved via
  `import`, compiling the template on an instance
   retrieved via `require`
- using `@roundingwellos/babel-plugin-handlebars-inline-precompile` to load plugins inline
2019-09-20 18:58:17 +02:00
Paul Falgout c55a7bed78 fix: use cjs for browser fields
We should not be using pre-built files for the `browser`.  Resolves https://github.com/wycats/handlebars.js/issues/1553
2019-09-20 18:54:42 +02:00
Nils Knappmeier 35d3fdb1dd Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package-lock.json
#	package.json
2019-09-03 22:24:48 +02:00
Nils Knappmeier dce44541db Update jsfiddle-link 2019-09-03 22:10:15 +02:00
Nils Knappmeier 164c7ceea4 v4.2.0 2019-09-03 21:58:07 +02:00
Nils Knappmeier 6ab48d8def Update release notes 2019-09-03 21:57:30 +02:00
Nils Knappmeier 8ac20285f6 Merge pull request #1534 from AndrewLeedham/4.x
Add VM TS mapping
2019-09-03 21:45:31 +02:00
Nils Knappmeier 888750ec27 fix typings of resolvePartial-options
- derived from the object structure seen in the debugger

closes #1534
2019-09-03 21:40:21 +02:00
Andrew Leedham 133b96a2ff Add "Handlebars.VM.resolvePartial" to type definitions
- Handlebars.VM is actually not part of the API,
  but Handlebars.VM.resolvePartial is mentioned
  in the documentation and is thus now treated
  as part of the API.

Closes #1534
2019-09-03 21:33:35 +02:00
Nils Knappmeier f119497312 chore: attempt to fix saucelabs problems with custom lib
- custom "grunt-saucelabs"
- which uses custom "sauce-tunnel"
- which uses current "sauce connect proxy"
2019-08-31 23:58:21 +02:00
Nils Knappmeier 62b64ecc3d chore: add comment to integration test 2019-08-31 23:53:28 +02:00
Nils Knappmeier a57b6824e0 add webpack test 2019-08-31 21:00:14 +02:00
Nils Knappmeier a26633f204 chore: fix integration tests 2019-08-31 19:27:11 +02:00
Nils Knappmeier f9cce4dd02 chore: add framework for various integration tests 2019-08-31 13:18:47 +02:00
Nils Knappmeier 16572cd848 feat: enhance typescript definition for knownHelpers
- Add support for custom helpers while keeping
  the list of builtin-helpers to maintain for
  autocompletion support

closes #1544
2019-08-29 22:53:09 +02:00
Nils Knappmeier a510d82252 Merge pull request #1530 from jbmusso/patch-2
Fix typo in compiler API doc: "mutation" field should read as "mutating"
2019-07-02 20:55:20 +02:00
Jean-Baptiste Musso 77e38330e6 Fix typo in compiler API doc: "mutation" field should read as "mutating"
The `Visitor` class expects a `boolean` property named `mutating`, instead of `mutation`.
2019-06-18 07:51:15 +02:00
Nils Knappmeier 3ce4425056 chore: udpate package-lock.json 2019-04-13 16:51:22 +02:00
Nils Knappmeier 2f8aa1c839 chore: update jsfiddle link 2019-04-13 16:49:52 +02:00
Nils Knappmeier 2f681d256f Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2019-04-13 16:48:27 +02:00
manishsaraan 1b04524a53 apiDoc added in list 2019-03-14 20:31:42 +01:00
Nils Knappmeier 3fc7e6feb2 chore: add text about security advisories to github templates 2019-02-17 20:50:34 +01:00
Nils Knappmeier 16c654579a docs: include typings in PR-template 2019-02-08 12:49:10 +01:00
Nils Knappmeier f349aa43c0 Merge branch '4.x'
# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package.json
2019-02-07 11:06:49 +01:00
Nils Knappmeier 913b845aa9 chore: bump components/handlebars.js versions to 5.0.0-alpha.1 2019-01-02 01:18:18 +01:00
Nils Knappmeier 920bf1c5ad Merge branch '4.x' 2019-01-02 01:09:10 +01:00
Nils Knappmeier d1eca1c5cf Merge branch '4.x' 2018-12-16 00:03:20 +01:00
Sharang Dashputre d7d14ed6e5 Remove duplicate text in release-notes.md
[ci skip]
2018-09-11 06:30:47 +02:00
Nils Knappmeier 379172e236 Merge branch '4.x' 2018-09-04 20:57:30 +02:00
Nils Knappmeier 148b19182d Merge branch '4.x' 2018-05-31 23:16:44 +02:00
Qiang Li e1fa310101 unnecessary check 2018-03-31 08:22:20 +02:00
Nils Knappmeier 94419deb95 Merge branch '4.x' 2017-11-28 22:16:54 +01:00
Nils Knappmeier 63be1c8130 Merge branch '4.x' 2017-11-28 22:02:33 +01:00
Nils Knappmeier 7f3da649c0 Merge branch '4.x' 2017-11-09 10:47:04 +01:00
Nils Knappmeier f21f900cf5 Merge branch '4.x'
# Conflicts:
#	.eslintrc.js
#	lib/precompiler.js
#	package.json
#	release-notes.md
2017-11-09 10:42:54 +01:00
Lukas Drgon 999220bf2d Add jsDelivr hits badge 2017-10-17 23:23:43 +02:00
Nils Knappmeier d5caa56d6c Gracefully handle missing uglify-js dependency
closes #1391

uglify-js is an optional dependency and should be treated as such.
This commit gracefully handles MODULE_NOT_FOUND errors while loading
uglify.

- Check for existing uglify-js (and load uglify-js) only if minification
  was activated
- Use "require.resolve" to check if uglify exists. Otherwise, a missing
  dependency of uglify-js would cause the same behavior as missing
  uglify-js. (Only a warning, no error)
- The code to load and run uglify is put into a single for readability
  purposes
- Tests use a mockup Module._resolveFilename to simulate the missing module.
  This function is used by both "require" and "require.resolve", so both
  are mocked equally.
2017-10-17 22:17:37 +02:00
Nils Knappmeier ce3cd8a685 Extend compiler-api example by replacing child-compiler
closes #1376
2017-10-06 23:45:36 +02:00
Marcos Marado 33773c2cb2 Update (C) year in the LICENSE file
Welcome to 2017!
2017-10-06 23:44:33 +02:00
Nils Knappmeier 7b74175e0e Merge branch '4.x' 2017-08-23 22:31:54 +02:00
Mike c933387415 Fix broken header tag (markdown)
The # was too close to the header text.
2017-08-18 00:16:22 +02:00
John Kreitlow 1a78717286 Fix Commits links in release-notes 2017-08-03 00:22:11 +02:00
Davide Mancuso 64002dc506 Update README.markdown
Add incremental-bars to the list of projects using Handlebars
2017-05-31 21:21:47 +02:00
Nils Knappmeier fb26f69485 Merge branch '4.x'
# Conflicts:
#	CONTRIBUTING.md
#	lib/handlebars/compiler/compiler.js
#	package.json
#	spec/compiler.js
2017-05-21 14:20:32 +02:00
Nils Knappmeier be107d2ca1 Merge branch '4.x'
# Conflicts:
#	tasks/test.js
2017-05-15 00:12:54 +02:00
Nils Knappmeier 2441ba1335 Merge branch '4.x' into wycats-master 2017-05-14 22:52:24 +02:00
Nils Knappmeier 5c676a32fb Merge remote-tracking branch 'wycats/4.x' into wycats-master 2017-05-14 14:48:44 +02:00
Nils Knappmeier 228950169d Update jsfiddle-link in CONTRIBUTING.md to 4.0.8 2017-05-13 08:33:14 +02:00
Nils Knappmeier 680ec9666a Merge branch '4.x' into wycats-master
# Conflicts:
#	package.json
2017-05-13 01:00:35 +02:00
Nils Knappmeier 58ec656fbc Bump version in master-branch to 5.0-alpha.1
This is in order to make sure that the master-branch version is not
confused with the stable 4.x
2017-04-30 19:30:09 +02:00
Nils Knappmeier 77674e4f6c Merge branch '4.x' into wycats-master 2017-04-30 19:26:21 +02:00
Nils Knappmeier 714a4c4482 Merge branch '4.x' into wycats-master
# Conflicts:
#	spec/regressions.js
2017-03-25 15:04:37 +01:00
Nils Knappmeier d40cbfc70d Merge pull request #1325 from robinhouston/pr-whitespace-differences
Document whitespace compatibility difference with mustache

Fixes #1324
2017-03-14 00:05:31 +01:00
Robin Houston b6e2f76a45 Document whitespace compatibility difference with mustache 2017-03-13 22:03:14 +00:00
Yehuda Katz 1b78b5b7b2 Merge pull request #1323 from chancancode/patch-1
Update README to be more accurate
2017-03-10 15:33:03 -08:00
Godfrey Chan a5e6afb05d Update README to be more accurate
Copied from the website at @wycats' request.

Addresses #1119.
2017-03-10 15:32:34 -08:00
Nils Knappmeier 8baba76b64 Merge pull request #1315 from Turbo87/es6-parser
parser: Change suffix to use ES6 default module export
2017-02-24 08:36:30 +01:00
Tobias Bieniek 508347ea94 Parser: Change suffix to use ES6 default module export
- This export will be transpiled by Babel for the cjs distribution,
  but will enable others to  use a pure ES6 module distribution
- Instanbul: Ignore "parser.js" for coverage reporting. This file was ignored before
  via annotation, but this has no effect anymore due to the above change
- Remove istanbul annotation from `parser-prefix` (@nknapp)

Squashed by @nknapp
2017-02-24 08:28:51 +01:00
Nils Knappmeier ad341bfc1b Merge pull request #1306 from wycats/merge-4.x
Merge 4.x into master
2017-02-17 08:43:27 +01:00
Nils Knappmeier 911e676af4 Merge branch '4.x'
# Conflicts:
#	Gruntfile.js
#	lib/handlebars/exception.js
#	spec/partials.js
#	spec/regressions.js
2017-02-14 23:10:02 +01:00
Nils Knappmeier 0fa10b6e87 Merge branch '4.x'
# Conflicts:
#	Gruntfile.js
#	lib/handlebars/exception.js
#	spec/partials.js
#	spec/regressions.js
2017-02-14 22:41:46 +01:00
Joonas Lahtinen 660a117536 Avoid duplicate "sourceMappingURL=" lines.
Avoid duplicate // sourceMappingURL=... lines when minifying AND
generating a map. UglifyJS2 will write the line when minifying.
2017-02-14 21:57:00 +01:00
Nils Knappmeier c9970200a0 Fix compiler-api example (#1303)
Closes #1302 

While trying to answer #1302, I noticed that the compiler-api example did not work in (because `nameLookup` is actually at the prototype). This example has a jsfiddle that proves it is working.
It does not provide an solve the exact problem of the reporter, but it answers the question.
2017-01-29 07:11:01 +01:00
Travis Nelson 6300e57f11 Moved .eslintrc to .eslintrc.js and cleaned up rules
Extending from eslint:recommended so we stay up to date on the latest
recommended rules.
2017-01-16 23:19:57 +01:00
Ryunosuke Sato 6c89939af7 Fix README filename in package.json
There is `README.markdown` in handlebars.js repository, not a `README.md`.
2017-01-15 22:21:04 +01:00
Marcos Marado a6476739f0 add more machine-readable data about the license
This small change is meant to make LibreJS happy.

Closes: #1273
2017-01-06 11:26:04 +01:00
Nils Knappmeier 9119265204 Added ".editorconfig"-file that specifies the indent for javascript-files (#1292)
* see http://editorconfig.org/
* This file only includes very basic settings. It may be expanded in the future
2017-01-06 10:14:59 +01:00
Nils Knappmeier d117b3e0f9 Eslint upgrade to 19.0.0 and configuration fixes (#1294)
Original PR by @travnels, commit-message edited by @nknapp

* Upgraded eslint to 19.0.0
* Cleaned up duplicate rules 'no-extra-parens', 'quote-props'
* eslint rule 'no-empty-label' was replaced.
  Rule 'no-empty-label' was removed and replaced by: ‘no-labels’. ‘no-labels’ already in the project
* eslint rule 'space-after-keywords' has been replaced
  Rules 'space-after-keywords' and 'space-return-throw-case' wer removed and replaced by ‘keyword-spacing’.
* Added parsoer-option: sourceType='module'
* Add unnecessary=false to 'quote-props' to remove warnings about unnecessarily quoted property. 

Code corrections
* helpers.js: unused variable 'depthString' removed, detected by new eslint
2017-01-06 10:03:46 +01:00
Marcos Marado 2b7ed7e0bb Fix istanbul dev dependency
According to npm's documentation[1], dependencies on github
repositories are declared with the following syntax:
    "module": "person/module"

This commit changes the package.json syntax on istanbul's dependency,
to follow the documentation.

[1] https://docs.npmjs.com/files/package.json#github-urls
2017-01-01 20:59:46 +01:00
Travis Nelson 9a36966b16 require('sys') is deprecated, using 'util' instead
(node:30288) DeprecationWarning: sys is deprecated. Use util instead.
2017-01-01 20:44:56 +01:00
Nils Knappmeier 6c92579faf Template for issues and pull-request
I've noticed that many issues are answered with "Please provide a jsfiddle".
This issue template tells the reporter to do that from the start.
2016-12-30 16:36:24 +01:00
Yehuda Katz 7535e48a79 Merge pull request #1243 from lawnsea/nested-partial-blocks
Walk up data frames for nested @partial-block
2016-09-10 07:10:36 -07:00
Kevin Decker 11b99b361d Merge pull request #1244 from blikblum/fix-build-windows
Fix build in windows - WIP
2016-08-23 07:12:06 -05:00
Luiz Américo 275ab37b2e Fix build in windows 2016-08-21 17:50:20 -03:00
Kevin Decker bff3af52f2 Merge pull request #1234 from blikblum/upgarde-jison
Upgrade jison to 0.4.16
2016-08-21 14:14:27 -05:00
Kevin Decker 9fb7baddcc Merge pull request #1239 from blikblum/patch-1
Update definition of BlockStatement.path
2016-08-21 14:12:35 -05:00
Lon Ingram 21696005cd Use XML-like tags in test instead of bizarre dot delimiters 2016-08-17 13:36:16 -05:00
Lon Ingram 601ce8a69f Add test reproducing #1185 2016-08-17 13:32:50 -05:00
Lon Ingram bbe0a94d6e Walk up data frames for nested @partial-block
The root cause of #1218 is that `invokePartial` creates a stack of data frames
for nested partial blocks, but `resolvePartial` always uses the value at top of
the stack without "popping" it. The result is an infinite recursive loop, as
references to `@partial-block` in the partial at the top of the stack resolve to
itself.

So, walk up the stack of data frames when evaluating. This is accomplished by
1) setting the `partial-block` property to `noop` after use and
2) using `_parent['partial-block']` if `partial-block` is `noop`

Fix #1218
2016-08-16 18:23:53 -05:00
Luiz Américo f2211ddd65 Update definition of BlockStatement.path
As pointed in https://github.com/wycats/handlebars.js/issues/1237 BlockStatement.path accepts in practice PathExpression or Literal.  
Updates its definition to reflect this fact
2016-07-21 20:45:25 -03:00
Luiz Américo e8722e2551 stick to 0.4.16. Tests does no pass with 0.4.17 2016-07-09 11:55:03 -03:00
Luiz Américo 094a2564fe upgrade jison to 0.4.17 2016-07-09 10:12:11 -03:00
Kevin Decker a6a0e50551 Merge pull request #1210 from kabirbaidhya/patch-1
Add a new lightweight package based on handlebars in the README
2016-05-17 12:20:44 -05:00
Kevin Decker 5c9d5b5b54 Merge pull request #1213 from jbboehr/hotfix/yargs-check
Fix test failure caused by yargs
2016-05-17 12:20:24 -05:00
John Boehr f0d4159a5e Fix test failure caused by yargs
In yargs 3.2, any non-truthy return value to check will fail: https://github.com/yargs/yargs/blob/master/CHANGELOG.md#302-20150213-1650-0000
It appears that this check is no longer necessary as the other contents of the check callback no longer exist: https://github.com/wycats/handlebars.js/commit/2e7a3bd70223c0e532a24290c7d817b951354541#diff-2aff13863648736946e83d70c5e63520L86
It appears the build failed for PR #1180, not sure why it was merged
2016-04-21 15:41:07 -07:00
Kabir 2ff795cf2f Add a new lightweight package based on handlebars in the README
Add a new package [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) on the list. 

This is a lightweight package that offers a collection of common handlebars helpers. And it is [fully tested](https://codecov.io/github/leapfrogtechnology/just-handlebars-helpers)
2016-04-12 16:17:12 +05:45
Kevin Decker e8ceafa652 Merge pull request #1191 from leo/patch-1
Added cory
2016-03-11 22:23:42 -06:00
Kevin Decker 6b644802d9 Merge pull request #1180 from TrySound/yargs
Use yargs
2016-03-11 22:23:00 -06:00
kpdecker 768ddbd661 Use objects for hash value tracking
The use of arrays was incorrect for the data type and causing problems when hash keys conflicted with array behaviors.

Fixes #1194
2016-03-11 22:16:18 -06:00
Kevin Decker 06baeaebfd Merge pull request #1187 from charleso/bugfix/1186-existing-program-block-param
Ensure that existing blockParams and depths are respected on dupe programs
2016-03-07 16:04:00 -06:00
Charles O'Farrell c7be766902 Ensure that existing blockParams and depths are respected on dupe programs
Fixes #1186
2016-03-08 07:23:45 +11:00
Mr. Leo b37232fc8d Added cory 2016-02-27 12:11:25 +01:00
Bogdan Chadkin 0757843712 Use yargs 2016-02-11 20:59:42 +03:00
Kevin Decker 27212f5953 Merge pull request #1177 from gennadiylitvinyuk/patch-1
Preserve License info in Closure Compiler
2016-02-05 08:31:59 -06:00
Gennadiy Litvinyuk 7130a11539 Preserve License info in Closure Compiler
To preserve license info in Closure Compiler the license has to be JSDoc-comment (not simple comment) and have @license before license text
2016-02-05 15:03:19 +01:00
Kevin Decker 4788b03192 Merge pull request #1171 from paulfalgout/patch-1
Contributing doc fix: failing thats -> failing tests
2016-01-20 02:05:46 -06:00
Paul Falgout 0b8fd1d8a8 Contributing doc fix: failing thats -> failing tests 2016-01-19 16:21:50 -06:00
Kevin Decker 89c8b3a886 Merge pull request #1166 from timwangdev/patch-1
Update license date
2016-01-05 21:46:51 -06:00
Tim Wang b55a120e82 Update license date 2016-01-06 10:47:29 +08:00
kpdecker dfc75545fa Update jsfiddle to point to latest 2015-12-26 18:49:28 -06:00
kpdecker 400bada31f Avoid multiple babel execution cycles 2015-12-26 18:49:28 -06:00
kpdecker 834695e241 Remove cdnjs copy task 2015-12-26 18:49:28 -06:00
kpdecker 5302782c08 Drop removed amd task from watch list 2015-12-26 18:49:28 -06:00
kpdecker 118836f390 Throw exception if id tracking args are passed
Fixes #1151
2015-12-26 18:49:28 -06:00
Kevin Decker ee6fadffb4 Merge pull request #1163 from adjohnson916/patch-1
Fix typos on decorators-api.md.
2015-12-24 09:15:34 -06:00
Anders D. Johnson ec64cf9293 Fix typos on decorators-api.md. 2015-12-23 15:28:46 -06:00
kpdecker 9a94d1771f Restore babel compile options 2015-12-14 02:52:38 -06:00
kpdecker 164f1c13f7 Use istanbul fork for proper line coverage 2015-12-14 02:46:42 -06:00
kpdecker 8517352e20 Remove amd files from the build
Users can utilize the UMD library if they are still using require.js and if they need to have specific modules, they can consume the cjs or es6 modules via webpack or similar.

Fix for #1096
2015-12-14 02:31:13 -06:00
kpdecker 63fdb92472 Drop extra Error params
This was causing a difficult to diagnose failure under IE and doesn’t give us enough value to justify the change.
2015-12-14 02:24:06 -06:00
Kevin Decker 8a9c79b799 Merge pull request #1153 from ryanmurakami/master
Adding documentation for running tests
2015-12-14 01:33:23 -06:00
Ryan Lewis eb1ec52b45 moving test documentation to contributing.md 2015-12-13 21:42:54 -08:00
Ryan Lewis 21bf95c92a Merge branch 'master' of https://github.com/ryanmurakami/handlebars.js 2015-12-13 20:42:13 -08:00
Ryan Lewis cc0b239aaf Adding documentation for running tests 2015-12-13 20:40:50 -08:00
kpdecker a6121cae79 Avoid error in older browsers in test
The tests are run through the transpiler and just reverting the user of template literal is easier than adding transpiler to the test stack.
2015-12-12 17:09:24 -06:00
kpdecker 871c32a6f9 Update target browser test versions 2015-12-12 16:26:48 -06:00
kpdecker 326734b0f3 Exclude coverage check in exception conditional 2015-12-12 16:14:17 -06:00
kpdecker 2ea6119a83 Fix throw when creating exception object in Safari
https://github.com/jquery/esprima/issues/1290
2015-12-12 16:07:52 -06:00
kpdecker 8289c0bf3a Update build for modern node versions 2015-12-12 15:34:06 -06:00
kpdecker 25458fdc2a Relax depth check for context push
Fixes #1135
2015-12-12 15:27:56 -06:00
kpdecker 63a08890cc Remove semi-documented _setup and _child APIs
These were a bad idea to begin with and without the trackIds implementation they don’t make much sense.
2015-12-12 15:27:26 -06:00
Kevin Decker 0f054efcdb Merge pull request #1148 from machty/rm-trackids-stringparams
Remove stringParams and trackIds mode
2015-12-10 14:04:34 -06:00
machty 3f77b82ed0 Remove stringParams and trackIds mode
Closes #1145
2015-12-10 14:22:45 -05:00
238 changed files with 30990 additions and 11418 deletions
+11
View File
@@ -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
+23
View File
@@ -0,0 +1,23 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
# JS-Snippets
src/*.js
+49 -114
View File
@@ -1,129 +1,64 @@
module.exports = {
"extends": "eslint:recommended",
"globals": {
"self": false
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
globals: {
self: false,
},
"env": {
"node": true
env: {
node: true,
es2020: true,
},
"ecmaFeatures": {
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
"arrowFunctions": true,
"blockBindings": true,
"defaultParams": true,
"destructuring": true,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"restParams": true,
"spread": true,
"templateStrings": true
parserOptions: {
sourceType: 'module',
},
"rules": {
// overrides eslint:recommended defaults
"no-sparse-arrays": "off",
"no-func-assign": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-unreachable": "warn",
// Possible Errors //
//-----------------//
"no-unsafe-negation": "error",
rules: {
'no-console': 'warn',
// temporarily disabled until the violating places are fixed.
'no-func-assign': 'off',
'no-sparse-arrays': 'off',
// Best Practices //
//----------------//
"curly": "error",
"default-case": "warn",
"dot-notation": ["error", { "allowKeywords": false }],
"guard-for-in": "warn",
"no-alert": "error",
"no-caller": "error",
"no-div-regex": "warn",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-floating-decimal": "error",
"no-implied-eval": "error",
"no-iterator": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-loop-func": "error",
"no-multi-spaces": "error",
"no-multi-str": "warn",
"no-global-assign": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-process-env": "error",
"no-proto": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",
'default-case': 'warn',
'guard-for-in': 'warn',
'no-alert': 'error',
'no-caller': 'error',
'no-div-regex': 'warn',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-floating-decimal': 'error',
'no-implied-eval': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-multi-str': 'warn',
'no-global-assign': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-octal-escape': 'error',
'no-process-env': 'error',
'no-proto': 'error',
'no-return-assign': 'error',
'no-script-url': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-unused-expressions': 'error',
'no-warning-comments': 'warn',
'no-with': 'error',
radix: 'error',
// Variables //
//-----------//
"no-catch-shadow": "error",
"no-label-var": "error",
"no-shadow-restricted-names": "error",
"no-undef-init": "error",
"no-use-before-define": ["error", "nofunc"],
// Stylistic Issues //
//------------------//
"comma-dangle": ["error", "never"],
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "error",
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"consistent-this": ["warn", "self"],
"eol-last": "error",
"func-style": ["error", "declaration"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true
}],
"new-cap": "error",
"new-parens": "error",
"no-array-constructor": "error",
"no-lonely-if": "error",
"no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn",
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-extra-parens": ["error", "functions"],
"quotes": ["error", "single", "avoid-escape"],
"semi": "error",
"semi-spacing": ["error", { "before": false, "after": true }],
"keyword-spacing": "error",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": ["error", "always", { "markers": [","] }],
"wrap-regex": "warn",
'no-label-var': 'error',
'no-undef-init': 'error',
'no-use-before-define': ['error', 'nofunc'],
// ECMAScript 6 //
//--------------//
"no-var": "warn"
'no-var': 'error',
},
"parserOptions": {
"sourceType": "module"
}
}
};
+4
View File
@@ -0,0 +1,4 @@
# Upgrade to Prettier 2.7
3d228334530860a6e3f99dc10777c84bf22292c1
# Format markdown files with Prettier
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
+9
View File
@@ -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.
+12
View File
@@ -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.
+9
View File
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
open-pull-requests-limit: 0
schedule:
interval: weekly
allow:
- dependency-type: production
+112
View File
@@ -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
+38
View File
@@ -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 }}
+11 -7
View File
@@ -1,14 +1,18 @@
vendor
.rvmrc
.DS_Store
lib/handlebars/compiler/parser.js
/dist/
/tmp/
/coverage/
node_modules
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
/yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
/coverage/
/dist/
/tests/integration/*/dist/
/spec/tmp/*
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "spec/mustache"]
path = spec/mustache
url = git://github.com/mustache/spec.git
url = https://github.com/mustache/spec.git
-2
View File
@@ -1,2 +0,0 @@
instrumentation:
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
+21
View File
@@ -0,0 +1,21 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
-27
View File
@@ -1,27 +0,0 @@
language: node_js
before_install:
- npm install -g grunt-cli
script:
- grunt --stack travis
- multi-nodejs-test/run-tests.sh 0.10 0.12 4 5 6 7 8 9 10 11
email:
on_failure: change
on_success: never
env:
global:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
matrix:
include:
- node_js: '10'
env:
- PUBLISH=true
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
cache:
directories:
- node_modules
git:
depth: 100
+59 -31
View File
@@ -1,26 +1,35 @@
# 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
- 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
We also accept [pull requests][pull-request]!
Generally we like to see pull requests that
- 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](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
@@ -31,8 +40,8 @@ Generally we like to see pull requests that
To build Handlebars.js you'll need a few things installed.
* Node.js
* [Grunt](http://gruntjs.com/getting-started)
- Node.js
- [Grunt](http://gruntjs.com/getting-started)
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
@@ -46,16 +55,18 @@ 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.
```sh
npm install
```
Clone the mustache specs into the spec/mustache folder.
```sh
cd spec
rm -r mustache
@@ -63,36 +74,47 @@ git clone https://github.com/mustache/spec.git mustache
```
From the root directory, run the tests.
```sh
npm test
```
## Ember testing
## Linting and Formatting
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.
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
We do linting and formatting in two phases:
```sh
npm link
grunt build release
cp dist/*.js $emberRepoDir/bower_components/handlebars/
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
while warnings are allowed.
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
are forbidden.
cd $emberRepoDir
npm link handlebars
npm test
```
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 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).
## 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:
```
yo release
npm ci
npx grunt
npm publish
yo release:publish components handlebars.js dist/components/
cd dist/components/
gem build handlebars-source.gemspec
@@ -102,14 +124,20 @@ gem push handlebars-source-*.gem
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version
* [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
* [The bower package](https://github.com/components/handlebars.js) (check the package.json)
* [The AWS S3 Bucket](http://builds.handlebarsjs.com.s3.amazonaws.com/) (check latest-tag)
* [RubyGems](https://rubygems.org/gems/handlebars-source)
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
- [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/
+31 -37
View File
@@ -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.
+92 -164
View File
@@ -1,151 +1,109 @@
/* eslint-disable no-process-env */
module.exports = function(grunt) {
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
eslint: {
options: {
},
files: [
'*.js',
'bench/**/*.js',
'tasks/**/*.js',
'lib/**/!(*.min|parser).js',
'spec/**/!(*.amd|json2|require).js',
'multi-nodejs-test/*.js'
]
},
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
clean: ['tmp', 'dist', 'tests/integration/**/node_modules'],
copy: {
dist: {
options: {
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;
}
processContent: function (content) {
return (
grunt.template.process(
'/**!\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: [
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
]
}
{
expand: true,
cwd: 'components/',
src: ['**'],
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/'
}]
}
files: [
{
cwd: 'lib/',
expand: true,
src: '**/!(index).js',
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: [{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
}
}]
}
files: [
{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
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: {
@@ -153,96 +111,66 @@ 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: {
checkTypes: {
cmd: 'npm run checkTypes',
bg: false,
fail: true
}
shell: {
integrationTests: {
command: './tests/integration/run-integration-tests.sh',
},
},
watch: {
scripts: {
options: {
atBegin: true
atBegin: true,
},
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['build', 'amd', 'tests', 'test']
}
}
tasks: ['on-file-change'],
},
},
});
// Build a new version of the library
this.registerTask('build', 'Builds a distributable version of the current project', [
'eslint',
'bgShell:checkTypes',
'parser',
'node',
'globals']);
this.registerTask('amd', ['babel:amd', 'requirejs']);
this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);
this.registerTask('tests', ['concat:tests']);
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
// Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean');
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('grunt-eslint');
grunt.loadNpmTasks('grunt-saucelabs');
grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks');
grunt.registerTask('bench', ['metrics']);
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
grunt.registerTask('node', ['babel:cjs']);
grunt.registerTask('globals', ['webpack']);
grunt.registerTask('release', 'Build final packages', [
'uglify',
'test:min',
'copy:dist',
'copy:components',
]);
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
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',
'shell:integrationTests',
]);
};
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011-2017 by Yehuda Katz
Copyright (C) 2011-2019 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+61 -61
View File
@@ -1,28 +1,29 @@
[![Travis Build Status](https://img.shields.io/travis/wycats/handlebars.js/master.svg)](https://travis-ci.org/wycats/handlebars.js)
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/wycats/handlebars.js?branch=master&svg=true)](https://ci.appveyor.com/project/wycats/handlebars-js)
[![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars)
[![CI Build Status](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml/badge.svg)](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/handlebars/badge?style=rounded)](https://www.jsdelivr.com/package/npm/handlebars)
[![npm downloads](https://badgen.net/npm/dm/handlebars)](https://www.npmjs.com/package/handlebars)
[![npm version](https://badgen.net/npm/v/handlebars)](https://www.npmjs.com/package/handlebars)
[![Bundle size](https://badgen.net/bundlephobia/minzip/handlebars?label=minified%20%2B%20gzipped)](https://bundlephobia.com/package/handlebars)
[![Install size](https://packagephobia.com/badge?p=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
[http://www.handlebarsjs.com](http://www.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
----------
See our [installation documentation](http://handlebarsjs.com/installation.html).
See our [installation documentation](https://handlebarsjs.com/installation/).
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
@@ -46,50 +47,48 @@ var result = template(data);
// </ul>
```
Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
Precompiling Templates
----------------------
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](http://handlebarsjs.com/precompilation.html).
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
Differences Between Handlebars.js and Mustache
----------------------------------------------
Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work.
- [Nested Paths](http://handlebarsjs.com/#paths)
- [Helpers](http://handlebarsjs.com/#helpers)
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
- [Literal Values](http://handlebarsjs.com/#literals)
- [Delimited Comments](http://handlebarsjs.com/#comments)
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
- [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.
[![Selenium Test Status](https://saucelabs.com/browser-matrix/handlebars.svg)](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
View File
@@ -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.
-37
View File
@@ -1,37 +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 install
- cmd: npm install -g grunt-cli
# Post-install test scripts
test_script:
# Output useful info for debugging
- cmd: node --version
- cmd: npm --version
# Run tests
- cmd: grunt --stack travis
# 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}"
-14
View File
@@ -1,14 +0,0 @@
{
"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
}
}
-19
View File
@@ -1,19 +0,0 @@
var _ = require('underscore'),
templates = require('./templates');
module.exports = function(grunt, callback) {
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
var Handlebars = require('../lib');
var templateSizes = {};
_.each(templates, function(info, template) {
var src = info.handlebars,
compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length;
});
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
callback([templateSizes]);
};
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
helpers: {
foo: function() {
return '';
}
},
context: {
bar: true
},
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
};
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#names}}{{name}}{{/names}}'
};
-14
View File
@@ -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 %>
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
};
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
handlebars: '{{#each names}}{{../foo}}{{/each}}',
mustache: '{{#names}}{{foo}}{{/names}}',
eco: '<% for item in @names: %><%= @foo %><% end %>'
};
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' },
handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
};
-11
View File
@@ -1,11 +0,0 @@
module.exports = {
context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
partials: {
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
},
handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
dust: '{#peeps}{>variables/}{/peeps}',
mustache: '{{#peeps}}{{>variables}}{{/peeps}}'
};
-7
View File
@@ -1,7 +0,0 @@
module.exports = {
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
};
-130
View File
@@ -1,130 +0,0 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),
eco, dust, Handlebars, Mustache;
try {
dust = require('dustjs-linkedin');
} catch (err) { /* NOP */ }
try {
Mustache = require('mustache');
} catch (err) { /* NOP */ }
try {
eco = require('eco');
} catch (err) { /* NOP */ }
function error() {
throw new Error('EWOT');
}
function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name,
context = template.context,
partials = template.partials,
handlebarsOut,
compatOut,
dustOut,
ecoOut,
mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function(partial, partialName) {
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
});
handlebarsOut = handlebar(context, options);
bench('handlebars', function() {
handlebar(context, options);
});
compatOut = compat(context, options);
bench('compat', function() {
compat(context, options);
});
if (handlebarsOnly) {
return;
}
if (dust) {
if (template.dust) {
dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dustOut = out; });
bench('dust', function() {
dust.render(templateName, context, function() {});
});
} else {
bench('dust', error);
}
}
if (eco) {
if (template.eco) {
var ecoTemplate = eco.compile(template.eco);
ecoOut = ecoTemplate(context);
bench('eco', function() {
ecoTemplate(context);
});
} else {
bench('eco', error);
}
}
if (Mustache) {
var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache;
if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
bench('mustache', function() {
Mustache.to_html(mustacheSource, context, mustachePartials);
});
} else {
bench('mustache', error);
}
}
// Hack around whitespace until we have whitespace control
handlebarsOut = handlebarsOut.replace(/\s/g, '');
function compare(b, lang) {
if (b == null) {
return;
}
b = b.replace(/\s/g, '');
if (handlebarsOut !== b) {
throw new Error('Template output mismatch: ' + name
+ '\n\nHandlebars: ' + handlebarsOut
+ '\n\n' + lang + ': ' + b);
}
}
compare(compatOut, 'compat');
compare(dustOut, 'dust');
compare(ecoOut, 'eco');
compare(mustacheOut, 'mustache');
}
module.exports = function(grunt, callback) {
// Deferring load incase we are being run inline with the grunt build
Handlebars = require('../lib');
console.log('Execution Throughput');
runner(grunt, makeSuite, function(times, scaled) {
callback(scaled);
});
};
-128
View File
@@ -1,128 +0,0 @@
#!/usr/bin/env node
var optimist = require('optimist')
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
'f': {
'type': 'string',
'description': 'Output File',
'alias': 'output'
},
'map': {
'type': 'string',
'description': 'Source Map File'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
'c': {
'type': 'string',
'description': 'Exports CommonJS style, path to Handlebars module',
'alias': 'commonjs',
'default': null
},
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
},
'k': {
'type': 'string',
'description': 'Known helpers',
'alias': 'known'
},
'o': {
'type': 'boolean',
'description': 'Known helpers only',
'alias': 'knownOnly'
},
'm': {
'type': 'boolean',
'description': 'Minimize output',
'alias': 'min'
},
'n': {
'type': 'string',
'description': 'Template namespace',
'alias': 'namespace',
'default': 'Handlebars.templates'
},
's': {
'type': 'boolean',
'description': 'Output template function only.',
'alias': 'simple'
},
'N': {
'type': 'string',
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
'alias': 'name'
},
'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'
},
'r': {
'type': 'string',
'description': 'Template root. Base value that will be stripped from template names.',
'alias': 'root'
},
'p': {
'type': 'boolean',
'description': 'Compiling a partial template',
'alias': 'partial'
},
'd': {
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
},
'e': {
'type': 'string',
'description': 'Template extension.',
'alias': 'extension',
'default': 'handlebars'
},
'b': {
'type': 'boolean',
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
'alias': 'bom'
},
'v': {
'type': 'boolean',
'description': 'Prints the current compiler version',
'alias': 'version'
},
'help': {
'type': 'boolean',
'description': 'Outputs this message'
}
})
.wrap(120)
.check(function(argv) {
if (argv.version) {
return;
}
});
var argv = optimist.argv;
argv.files = argv._;
delete argv._;
var Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function(err, opts) {
if (err) {
throw err;
}
if (opts.help || (!opts.templates.length && !opts.version)) {
optimist.showHelp();
} else {
Precompiler.cli(opts);
}
});
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env node
const yargs = require('yargs')
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...')
.option('f', {
type: 'string',
description: 'Output File',
alias: 'output',
})
.option('map', {
type: 'string',
description: 'Source Map File',
})
.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,
})
.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)',
alias: 'handlebarPath',
default: '',
deprecated: true,
})
.option('k', {
type: 'string',
description: 'Known helpers',
alias: 'known',
})
.option('o', {
type: 'boolean',
description: 'Known helpers only',
alias: 'knownOnly',
})
.option('m', {
type: 'boolean',
description: 'Minimize output',
alias: 'min',
})
.option('n', {
type: 'string',
description: 'Template namespace',
alias: 'namespace',
default: 'Handlebars.templates',
})
.option('s', {
type: 'boolean',
description: 'Output template function only.',
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',
})
.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',
})
.option('r', {
type: 'string',
description:
'Template root. Base value that will be stripped from template names.',
alias: 'root',
})
.option('p', {
type: 'boolean',
description: 'Compiling a partial template',
alias: 'partial',
})
.option('d', {
type: 'boolean',
description: 'Include data when compiling',
alias: 'data',
})
.option('e', {
type: 'string',
description: 'Template extension.',
alias: 'extension',
default: 'handlebars',
})
.option('b', {
type: 'boolean',
description:
'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
alias: 'bom',
})
.option('v', {
type: 'boolean',
description: 'Prints the current compiler version',
alias: 'version',
})
.option('help', {
type: 'boolean',
description: 'Outputs this message',
})
.wrap(120);
const argv = yargs.argv;
argv.files = argv._;
delete argv._;
const Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function (err, opts) {
if (err) {
throw err;
}
if (opts.help || (!opts.templates.length && !opts.version)) {
yargs.showHelp();
} else {
Precompiler.cli(opts);
}
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.1.2-0",
"version": "5.0.0-alpha.1",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
+1 -3
View File
@@ -3,7 +3,5 @@
"repo": "components/handlebars.js",
"version": "1.0.0",
"main": "handlebars.js",
"scripts": [
"handlebars.js"
]
"scripts": ["handlebars.js"]
}
+32 -32
View File
@@ -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"
}
}
}
}
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -2,10 +2,10 @@
<package>
<metadata>
<id>handlebars.js</id>
<version>4.1.2-0</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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.1.2-0",
"version": "5.0.0-alpha.1",
"license": "MIT",
"jspm": {
"main": "handlebars",
+60 -31
View File
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
Handlebars.precompile(ast);
```
### Parsing
There are two primary APIs that are used to parse an existing template into the AST:
#### parseWithoutProcessing
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
Example:
```js
let ast = Handlebars.parseWithoutProcessing(myTemplate);
```
#### parse
`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 `''`)
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
Example:
```js
let ast = Handlebars.parse(myTemplate);
```
### Basic
@@ -43,7 +71,7 @@ interface Position {
interface Program <: Node {
type: "Program";
body: [ Statement ];
blockParams: [ string ];
}
```
@@ -104,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";
@@ -120,7 +147,6 @@ interface CommentStatement <: Statement {
}
```
```java
interface Decorator <: Statement {
type: "Decorator";
@@ -181,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
@@ -214,7 +239,6 @@ interface NullLiteral <: Literal {
}
```
### Miscellaneous
```java
@@ -251,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);
};
@@ -263,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.
@@ -272,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
@@ -281,20 +305,20 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
- `depthedLookup(name)`
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
- `compilerInfo()`
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.
@@ -302,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);
@@ -310,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' }],
})
);
```
+4 -2
View File
@@ -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.
+7
View File
@@ -0,0 +1,7 @@
/* eslint-env node */
module.exports = {
env: {
// Handlebars should run natively in the browser
node: false,
},
};
+10 -4
View File
@@ -1,11 +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 } 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';
@@ -13,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);
};
@@ -25,6 +30,7 @@ function create() {
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;
return hb;
}
+2 -2
View File
@@ -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);
};
+34 -18
View File
@@ -1,11 +1,13 @@
import {createFrame, extend, toString} from './utils';
import Exception from './exception';
import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import { Exception } from '@handlebars/parser';
import { createFrame, extend, toString } from './utils';
import { registerDefaultHelpers } from './helpers';
import { registerDefaultDecorators } from './decorators';
import logger from './logger';
import { resetLoggedProperties } from './internal/proto-access';
export const VERSION = '4.1.2-0';
export const COMPILER_REVISION = 7;
export const VERSION = '4.7.7';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
export const REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
@@ -14,7 +16,8 @@ export const REVISION_CHANGES = {
4: '== 1.x.x',
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1',
7: '>= 4.0.0'
7: '>= 4.0.0 <4.3.0',
8: '>= 4.3.0',
};
const objectType = '[object Object]';
@@ -34,45 +37,58 @@ 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'); }
if (fn) {
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name);
} else {
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 {
if (typeof partial === 'undefined') {
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
throw new Exception(
`Attempting to register a partial called "${name}" as undefined`
);
}
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'); }
if (fn) {
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function(name) {
unregisterDecorator: function (name) {
delete this.decorators[name];
}
},
/**
* Reset the memory of illegal property accesses that have already been logged.
* @deprecated should only be used in handlebars test-cases
*/
resetLoggedPropertyAccesses() {
resetLoggedProperties();
},
};
export let log = logger.log;
export {createFrame, logger};
export { createFrame, logger };
+15 -11
View File
@@ -4,25 +4,29 @@ 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) {
return (node.type === 'SubExpression')
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
&& !!((node.params && node.params.length) || node.hash));
helperExpression: function (node) {
return (
node.type === 'SubExpression' ||
((node.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
},
scopedId: function(path) {
return (/^\.|this\b/).test(path.original);
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) {
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
}
}
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
// must modify the object to operate properly.
export default AST;
-24
View File
@@ -1,24 +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 parse(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 strip = new WhitespaceControl(options);
return strip.accept(parser.parse(input));
}
+46 -43
View File
@@ -1,14 +1,14 @@
/* global define */
import {isArray} from '../utils';
import { isArray } from '../utils';
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,28 +25,27 @@ 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() {
return {code: this.toString()};
toStringWithSourceMap: function () {
return { code: this.toString() };
},
toString: function() {
toString: function () {
return this.src;
}
},
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
@@ -62,7 +61,6 @@ function castChunk(chunk, codeGen, loc) {
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
@@ -72,67 +70,74 @@ 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() {
let loc = this.currentLocation || {start: {}};
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;
}
chunk = castChunk(chunk, this, loc);
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
return new SourceNode(
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
},
functionCall: function(fn, type, params) {
functionCall: function (fn, type, params) {
params = this.generateList(params);
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
},
quotedString: function(str) {
return '"' + (str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
quotedString: function (str) {
return (
'"' +
(str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') +
'"'
);
},
objectLiteral: function(obj) {
objectLiteral: function (obj) {
let pairs = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
Object.keys(obj).forEach((key) => {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
}
});
let ret = this.generateList(pairs);
ret.prepend('{');
@@ -140,8 +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++) {
@@ -155,14 +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;
+149 -172
View File
@@ -1,7 +1,7 @@
/* eslint-disable new-cap */
import Exception from '../exception';
import {isArray, indexOf, extend} from '../utils';
import { Exception } from '@handlebars/parser';
import { isArray, indexOf, extend } from '../utils';
import AST from './ast';
const slice = [].slice;
@@ -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;
@@ -24,8 +24,11 @@ Compiler.prototype = {
for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
otherOpcode = other.opcodes[i];
if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false;
}
}
@@ -44,44 +47,36 @@ 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 || [];
// These changes will propagate to the other compiler components
let knownHelpers = options.knownHelpers;
options.knownHelpers = {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
'if': true,
'unless': true,
'with': true,
'log': true,
'lookup': true
};
if (knownHelpers) {
// the next line should use "Object.keys", but the code has been like this a long time and changing it, might
// cause backwards-compatibility issues... It's an old library...
// eslint-disable-next-line guard-for-in
for (let name in knownHelpers) {
this.options.knownHelpers[name] = knownHelpers[name];
}
}
options.knownHelpers = extend(
Object.create(null),
{
helperMissing: true,
blockHelperMissing: true,
each: true,
if: true,
unless: true,
with: true,
log: true,
lookup: true,
},
options.knownHelpers
);
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++;
result = childCompiler.compile(program, this.options),
guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
@@ -91,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);
@@ -103,11 +98,11 @@ Compiler.prototype = {
return ret;
},
Program: function(program) {
Program: function (program) {
this.options.blockParams.unshift(program.blockParams);
let body = program.body,
bodyLength = body.length;
bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]);
}
@@ -120,11 +115,11 @@ Compiler.prototype = {
return this;
},
BlockStatement: function(block) {
BlockStatement: function (block) {
transformLiteralToPath(block);
let program = block.program,
inverse = block.inverse;
inverse = block.inverse;
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
@@ -159,13 +154,13 @@ Compiler.prototype = {
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
path = decorator.path;
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
},
PartialStatement: function(partial) {
PartialStatement: function (partial) {
this.usePartial = true;
let program = partial.program;
@@ -175,17 +170,20 @@ Compiler.prototype = {
let params = partial.params;
if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
throw new Exception(
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) {
if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined');
} else {
params.push({type: 'PathExpression', parts: [], depth: 0});
params.push({ type: 'PathExpression', parts: [], depth: 0 });
}
}
let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
@@ -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,16 +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);
@@ -239,10 +236,10 @@ 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;
name = path.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', path.depth);
@@ -255,38 +252,46 @@ 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];
path = sexpr.path,
name = path.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
throw new Exception(
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else {
path.strict = true;
path.falsy = true;
this.accept(path);
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
this.opcode(
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
}
},
PathExpression: function(path) {
PathExpression: function (path) {
this.addDepth(path.depth);
this.opcode('getContext', path.depth);
let name = path.parts[0],
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
@@ -297,34 +302,40 @@ Compiler.prototype = {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
}
},
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;
i = 0,
l = pairs.length;
this.opcode('pushHash');
@@ -338,11 +349,15 @@ Compiler.prototype = {
},
// HELPERS
opcode: function(name) {
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
opcode: function (name) {
this.opcodes.push({
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc,
});
},
addDepth: function(depth) {
addDepth: function (depth) {
if (!depth) {
return;
}
@@ -350,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]);
@@ -368,8 +383,7 @@ Compiler.prototype = {
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
let name = sexpr.path.parts[0],
options = this.options;
options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
@@ -386,59 +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);
@@ -454,77 +426,82 @@ Compiler.prototype = {
return params;
},
blockParamIndex: function(name) {
for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
blockParamIndex: function (name) {
for (
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name);
param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) {
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);
validateInput(input, options);
let compiled;
function compileInput() {
let environment = compileEnvironment(input, options, env),
templateSpec = new env.JavaScriptCompiler().compile(
environment,
options,
undefined,
true
);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
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
);
}
if (options.trackIds || options.stringParams) {
throw new Exception(
'TrackIds and stringParams are no longer supported. See Github #1145'
);
}
options = extend({}, options);
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
let compiled;
function compileInput() {
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
function ret(context, execOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, execOptions);
}
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;
}
function compileEnvironment(input, options, env) {
let ast = env.parse(input, options);
return new env.Compiler().compile(ast, options);
}
function argEquals(a, b) {
@@ -553,7 +530,7 @@ function transformLiteralToPath(sexpr) {
depth: 0,
parts: [literal.original + ''],
original: literal.original + '',
loc: literal.loc
loc: literal.loc,
};
}
}
-210
View File
@@ -1,210 +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)
};
}
File diff suppressed because it is too large Load Diff
-171
View File
@@ -1,171 +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 */
-129
View File
@@ -1,129 +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,216 +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 preceeded 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
View File
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
+22 -19
View File
@@ -1,22 +1,25 @@
import {extend} from '../utils';
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;
});
);
}
-49
View File
@@ -1,49 +0,0 @@
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
let loc = node && node.loc,
line,
column;
if (loc) {
line = loc.start.line;
column = loc.start.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;
// 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
});
} else {
this.column = column;
}
}
} catch (nop) {
/* Ignore if the browser is very particular */
}
}
Exception.prototype = new Error();
export default Exception;
+9
View File
@@ -15,3 +15,12 @@ export function registerDefaultHelpers(instance) {
registerLookup(instance);
registerWith(instance);
}
export function moveHelperToHooks(instance, helperName, keepHelper) {
if (instance.helpers[helperName]) {
instance.hooks[helperName] = instance.helpers[helperName];
if (!keepHelper) {
delete instance.helpers[helperName];
}
}
}
+4 -14
View File
@@ -1,9 +1,9 @@
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;
fn = options.fn;
if (context === true) {
return fn(this);
@@ -11,21 +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);
}
});
+35 -32
View File
@@ -1,25 +1,22 @@
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');
}
let fn = options.fn,
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;
inverse = options.inverse,
i = 0,
ret = '',
data;
if (options.data && options.ids) {
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
if (isFunction(context)) {
context = context.call(this);
}
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
@@ -30,16 +27,14 @@ 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])
});
ret =
ret +
fn(context[field], {
data: data,
blockParams: [context[field], field],
});
}
if (context && typeof context === 'object') {
@@ -49,21 +44,29 @@ export default function(instance) {
execIteration(i, i, i === context.length - 1);
}
}
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
const newContext = [];
const iterator = context[Symbol.iterator]();
for (let it = iterator.next(); !it.done; it = iterator.next()) {
newContext.push(it.value);
}
context = newContext;
for (let j = context.length; i < j; i++) {
execIteration(i, i, i === context.length - 1);
}
} else {
let priorKey;
for (let key in context) {
if (context.hasOwnProperty(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.
if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
priorKey = key;
i++;
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 intermediate keys array.
if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
}
priorKey = key;
i++;
});
if (priorKey !== undefined) {
execIteration(priorKey, i - 1, true);
}
+6 -4
View File
@@ -1,13 +1,15 @@
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;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
throw new Exception(
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
}
});
}
+20 -7
View File
@@ -1,11 +1,17 @@
import {isEmpty, isFunction} from '../utils';
import { Exception } from '@handlebars/parser';
import { isEmpty, isFunction } from '../utils';
export default function(instance) {
instance.registerHelper('if', function(conditional, options) {
if (isFunction(conditional)) { conditional = conditional.call(this); }
export default function (instance) {
instance.registerHelper('if', function (conditional, options) {
if (arguments.length != 2) {
throw new Exception('#if requires exactly one argument');
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// 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);
@@ -14,7 +20,14 @@ export default function(instance) {
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
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,
});
});
}
+4 -4
View File
@@ -1,7 +1,7 @@
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];
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
@@ -14,6 +14,6 @@ export default function(instance) {
}
args[0] = level;
instance.log(... args);
instance.log(...args);
});
}
+4 -6
View File
@@ -1,11 +1,9 @@
export default function(instance) {
instance.registerHelper('lookup', function(obj, field) {
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;
}
if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
return undefined;
}
return obj[field];
return options.lookupProperty(obj, field);
});
}
+11 -9
View File
@@ -1,21 +1,23 @@
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
import { Exception } from '@handlebars/parser';
import { isEmpty, isFunction } from '../utils';
export default function(instance) {
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
export default function (instance) {
instance.registerHelper('with', function (context, options) {
if (arguments.length != 2) {
throw new Exception('#with requires exactly one argument');
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn;
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);
@@ -0,0 +1,11 @@
import { extend } from '../utils';
/**
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
* The resulting object can be used with "object[property]" to check if a property exists
* @param {...object} sources a varargs parameter of source objects that will be merged
* @returns {object}
*/
export function createNewLookupObject(...sources) {
return extend(Object.create(null), ...sources);
}
+70
View File
@@ -0,0 +1,70 @@
import { createNewLookupObject } from './create-new-lookup-object';
import logger from '../logger';
const loggedProperties = Object.create(null);
export function createProtoAccessControl(runtimeOptions) {
let defaultMethodWhiteList = Object.create(null);
defaultMethodWhiteList['constructor'] = false;
defaultMethodWhiteList['__defineGetter__'] = false;
defaultMethodWhiteList['__defineSetter__'] = false;
defaultMethodWhiteList['__lookupGetter__'] = false;
let defaultPropertyWhiteList = Object.create(null);
// eslint-disable-next-line no-proto
defaultPropertyWhiteList['__proto__'] = false;
return {
properties: {
whitelist: createNewLookupObject(
defaultPropertyWhiteList,
runtimeOptions.allowedProtoProperties
),
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
},
methods: {
whitelist: createNewLookupObject(
defaultMethodWhiteList,
runtimeOptions.allowedProtoMethods
),
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
},
};
}
export function resultIsAllowed(result, protoAccessControl, propertyName) {
if (typeof result === 'function') {
return checkWhiteList(protoAccessControl.methods, propertyName);
} else {
return checkWhiteList(protoAccessControl.properties, propertyName);
}
}
function checkWhiteList(protoAccessControlForType, propertyName) {
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
return protoAccessControlForType.whitelist[propertyName] === true;
}
if (protoAccessControlForType.defaultValue !== undefined) {
return protoAccessControlForType.defaultValue;
}
logUnexpectedPropertyAccessOnce(propertyName);
return false;
}
function logUnexpectedPropertyAccessOnce(propertyName) {
if (loggedProperties[propertyName] !== true) {
loggedProperties[propertyName] = true;
logger.log(
'error',
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
`You can add a runtime option to disable the check or this warning:\n` +
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
);
}
}
export function resetLoggedProperties() {
Object.keys(loggedProperties).forEach((propertyName) => {
delete loggedProperties[propertyName];
});
}
+13
View File
@@ -0,0 +1,13 @@
export function wrapHelper(helper, transformOptionsFn) {
if (typeof helper !== 'function') {
// 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 */) {
const options = arguments[arguments.length - 1];
arguments[arguments.length - 1] = transformOptionsFn(options);
return helper.apply(this, arguments);
};
return wrapper;
}
+10 -6
View File
@@ -1,11 +1,11 @@
import {indexOf} from './utils';
import { indexOf } from './utils';
let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
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,17 +19,21 @@ let logger = {
},
// Can be overridden in the host environment
log: function(level, ...message) {
log: function (level, ...message) {
level = logger.lookupLevel(level);
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
if (
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level];
if (!console[method]) { // eslint-disable-line no-console
// eslint-disable-next-line no-console
if (!console[method]) {
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
}
}
},
};
export default logger;
+6 -8
View File
@@ -1,12 +1,10 @@
/* global window */
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;
};
+223 -76
View File
@@ -1,22 +1,48 @@
import { Exception } from '@handlebars/parser';
import * as Utils from './utils';
import Exception from './exception';
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
import {
COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES,
} from './base';
import { moveHelperToHooks } from './helpers';
import { wrapHelper } from './internal/wrapHelper';
import {
createProtoAccessControl,
resultIsAllowed,
} from './internal/proto-access';
export function checkRevision(compilerInfo) {
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
}
if (
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return;
}
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception(
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
'Please update your precompiler to a newer version (' +
runtimeVersions +
') or downgrade your runtime to an older version (' +
compilerVersions +
').'
);
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception(
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Please update your runtime to a newer version (' +
compilerInfo[1] +
').'
);
}
}
@@ -32,23 +58,38 @@ export function template(templateSpec, env) {
templateSpec.main.decorator = templateSpec.main_d;
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs.
// for external users to override these as pseudo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
const templateWasPrecompiledWithCompilerV7 =
templateSpec.compiler && templateSpec.compiler[0] === 7;
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 result = env.VM.invokePartial.call(this, partial, context, options);
let extendedOptions = Utils.extend({}, options, {
hooks: this.hooks,
protoAccessControl: this.protoAccessControl,
});
let result = env.VM.invokePartial.call(
this,
partial,
context,
extendedOptions
);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
result = options.partials[options.name](context, options);
options.partials[options.name] = env.compile(
partial,
templateSpec.compilerOptions,
env
);
result = options.partials[options.name](context, extendedOptions);
}
if (result != null) {
if (options.indent) {
@@ -64,61 +105,90 @@ export function template(templateSpec, env) {
}
return result;
} else {
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
throw new Exception(
'The partial ' +
options.name +
' could not be compiled when running in runtime-only mode'
);
}
}
// Just add water
let container = {
strict: function(obj, name) {
if (!(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj);
strict: function (obj, name, loc) {
if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, {
loc: loc,
});
}
return obj[name];
return container.lookupProperty(obj, name);
},
lookup: function(depths, name) {
lookupProperty: function (parent, propertyName) {
let result = parent[propertyName];
if (result == null) {
return result;
}
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return result;
}
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
return result;
}
return undefined;
},
lookup: function (depths, name) {
const len = depths.length;
for (let i = 0; i < len; i++) {
if (depths[i] && depths[i][name] != null) {
let result = depths[i] && container.lookupProperty(depths[i], name);
if (result != null) {
return depths[i][name];
}
}
},
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);
fn = this.fn(i);
if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
programWrapper = wrapProgram(
this,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
);
} else if (!programWrapper) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
}
return programWrapper;
},
data: function(value, depth) {
data: function (value, depth) {
while (value && depth--) {
value = value._parent;
}
return value;
},
merge: function(param, common) {
mergeIfNeeded: function (param, common) {
let obj = param || common;
if (param && common && (param !== common)) {
if (param && common && param !== common) {
obj = Utils.extend({}, common, param);
}
@@ -128,77 +198,126 @@ 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);
}
let depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
if (options.depths) {
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
depths =
context != options.depths[0]
? [context].concat(options.depths)
: options.depths;
} else {
depths = [context];
}
}
function main(context/*, options*/) {
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
function main(context /*, options*/) {
return (
'' +
templateSpec.main(
container,
context,
container.helpers,
container.partials,
data,
blockParams,
depths
)
);
}
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
main = executeDecorators(
templateSpec.main,
main,
container,
options.depths || [],
data,
blockParams
);
return main(context, options);
}
ret.isTop = true;
ret._setup = function(options) {
function _setup(options) {
if (!options.partial) {
container.helpers = container.merge(options.helpers, env.helpers);
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
wrapHelpersToPassLookupProperty(mergedHelpers, container);
container.helpers = mergedHelpers;
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
// Use mergeIfNeeded here to prevent compiling global partials multiple times
container.partials = container.mergeIfNeeded(
options.partials,
env.partials
);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = container.merge(options.decorators, env.decorators);
container.decorators = Utils.extend(
{},
env.decorators,
options.decorators
);
}
container.hooks = {};
container.protoAccessControl = createProtoAccessControl(options);
let keepHelperInHelpers =
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else {
container.protoAccessControl = options.protoAccessControl; // internal option
container.helpers = options.helpers;
container.partials = options.partials;
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;
}
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
export function wrapProgram(
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) {
let currentDepths = depths;
if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
if (
depths &&
context != depths[0] &&
!(context === container.nullContext && depths[0] === null)
) {
currentDepths = [context].concat(depths);
}
return fn(container,
context,
container.helpers, container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths);
return fn(
container,
context,
container.helpers,
container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths
);
}
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
@@ -209,6 +328,9 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
return prog;
}
/**
* This is currently part of the official API, therefore implementation details should not be changed.
*/
export function resolvePartial(partial, context, options) {
if (!partial) {
if (options.name === '@partial-block') {
@@ -228,17 +350,16 @@ 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) {
options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure
let fn = options.fn;
partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
context,
options = {}
) {
// Restore the partial-block from the closure for the execution of the block
// i.e. the part inside the block of the partial call.
options.data = createFrame(options.data);
@@ -255,13 +376,17 @@ 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);
}
}
export function noop() { return ''; }
export function noop() {
return '';
}
function initData(context, data) {
if (!data || !('root' in data)) {
@@ -274,8 +399,30 @@ function initData(context, data) {
function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) {
let props = {};
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
prog = fn.decorator(
prog,
props,
container,
depths && depths[0],
data,
blockParams,
depths
);
Utils.extend(prog, props);
}
return prog;
}
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
Object.keys(mergedHelpers).forEach((helperName) => {
let helper = mergedHelpers[helperName];
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
});
}
function passLookupPropertyOption(helper, container) {
const lookupProperty = container.lookupProperty;
return wrapHelper(helper, (options) => {
return Utils.extend({ lookupProperty }, options);
});
}
+1 -1
View File
@@ -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;
};
+15 -29
View File
@@ -5,17 +5,17 @@ const escape = {
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;',
'=': '&#x3D;'
'=': '&#x3D;',
};
const badChars = /[&<>"'`=]/g,
possible = /[&<>"'`=]/;
possible = /[&<>"'`=]/;
function escapeChar(chr) {
return escape[chr];
}
export function extend(obj/* , ...source */) {
export function extend(obj /* , ...source */) {
for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
@@ -30,25 +30,19 @@ 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) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
export const isArray =
Array.isArray ||
function (value) {
return value && typeof value === 'object'
? toString.call(value) === '[object Array]'
: false;
};
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
export function indexOf(array, value) {
@@ -60,7 +54,6 @@ export function indexOf(array, value) {
return -1;
}
export function escapeExpression(string) {
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
@@ -78,7 +71,9 @@ export function escapeExpression(string) {
string = '' + string;
}
if (!possible.test(string)) { return string; }
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
@@ -97,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
View File
@@ -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;
+135 -87
View File
@@ -1,17 +1,17 @@
/* eslint-env node */
/* eslint-disable no-console */
import Async from 'neo-async';
import fs from 'fs';
import * as Handlebars from './handlebars';
import {basename} from 'path';
import {SourceMapConsumer, SourceNode} from 'source-map';
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 {
@@ -25,14 +25,19 @@ module.exports.loadTemplates = function(opts, callback) {
function loadStrings(opts, callback) {
let strings = arrayCast(opts.string),
names = arrayCast(opts.name);
names = arrayCast(opts.name);
if (names.length !== strings.length
&& strings.length > 1) {
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
if (names.length !== strings.length && strings.length > 1) {
return callback(
new Handlebars.Exception(
'Number of names did not match the number of string inputs'
)
);
}
Async.map(strings, function(string, callback) {
Async.map(
strings,
function (string, callback) {
if (string !== '-') {
callback(undefined, string);
} else {
@@ -40,105 +45,124 @@ 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);
});
}
);
}
function loadFiles(opts, callback) {
// Build file extension pattern
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; });
let extension = (opts.extension || 'handlebars').replace(
/[\\^$*+?.():=!|{}\-[\]]/g,
function (arg) {
return '\\' + arg;
}
);
extension = new RegExp('\\.' + extension + '$');
let ret = [],
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
Async.whilst(() => queue.length, function(callback) {
let {template: path, root} = queue.shift();
queue = (opts.files || []).map((template) => ({
template,
root: opts.root,
}));
Async.whilst(
() => queue.length,
function (callback) {
let { template: path, root } = queue.shift();
fs.stat(path, function(err, stat) {
if (err) {
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
}
fs.stat(path, function (err, stat) {
if (err) {
return callback(
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
}
if (stat.isDirectory()) {
opts.hasDirectory = true;
if (stat.isDirectory()) {
opts.hasDirectory = true;
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) {
let childPath = path + '/' + file;
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
queue.push({template: childPath, root: root || path});
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) {
let childPath = path + '/' + file;
callback();
});
if (
extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({ template: childPath, root: root || path });
}
});
callback();
});
} else {
fs.readFile(path, 'utf8', function (err, data) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data,
});
callback();
});
}
});
},
function (err) {
if (err) {
callback(err);
} else {
fs.readFile(path, 'utf8', function(err, data) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data
});
callback();
});
callback(undefined, ret);
}
});
},
function(err) {
if (err) {
callback(err);
} else {
callback(undefined, ret);
}
});
);
}
module.exports.cli = function(opts) {
module.exports.cli = function (opts) {
if (opts.version) {
console.log(Handlebars.VERSION);
return;
}
if (!opts.templates.length && !opts.hasDirectory) {
throw new Handlebars.Exception('Must define at least one template or directory.');
throw new Handlebars.Exception(
'Must define at least one template or directory.'
);
}
if (opts.simple && opts.min) {
@@ -147,12 +171,18 @@ module.exports.cli = function(opts) {
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) {
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
throw new Handlebars.Exception(
'Unable to output multiple templates in simple mode'
);
}
// Force simple mode if we have only one template and it's unnamed.
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
&& !opts.templates[0].name) {
if (
!opts.amd &&
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true;
}
@@ -172,9 +202,15 @@ module.exports.cli = function(opts) {
let output = new SourceNode();
if (!opts.simple) {
if (opts.amd) {
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
output.add(
"define(['" +
opts.handlebarPath +
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
);
} 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');
}
@@ -188,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) {
@@ -206,7 +242,10 @@ module.exports.cli = function(opts) {
// If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) {
let consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
precompiled = SourceNode.fromStringWithSourceMap(
precompiled.code,
consumer
);
}
if (opts.simple) {
@@ -219,7 +258,14 @@ module.exports.cli = function(opts) {
if (opts.amd && !multiple) {
output.add('return ');
}
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
output.add([
objectName,
"['",
template.name,
"'] = template(",
precompiled,
');\n',
]);
}
});
@@ -286,13 +332,15 @@ function minify(output, sourceMapFile) {
throw e;
}
// it does not exist!
console.error('Code minimization is disabled due to missing uglify-js dependency');
console.error(
'Code minimization is disabled due to missing uglify-js dependency'
);
return output;
}
return require('uglify-js').minify(output.code, {
sourceMap: {
content: output.map,
url: sourceMapFile
}
url: sourceMapFile,
},
});
}
-114
View File
@@ -1,114 +0,0 @@
module.exports = {
"extends": "eslint:recommended",
"globals": {
"self": false
},
"env": {
"node": true
},
"rules": {
// overrides eslint:recommended defaults
"no-sparse-arrays": "off",
"no-func-assign": "off",
"no-console": "off",
"no-debugger": "warn",
"no-unreachable": "warn",
// Possible Errors //
//-----------------//
"no-unsafe-negation": "error",
// Best Practices //
//----------------//
"curly": "error",
"default-case": "warn",
"dot-notation": ["error", { "allowKeywords": false }],
"guard-for-in": "warn",
"no-alert": "error",
"no-caller": "error",
"no-div-regex": "warn",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-floating-decimal": "error",
"no-implied-eval": "error",
"no-iterator": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-loop-func": "error",
"no-multi-spaces": "error",
"no-multi-str": "warn",
"no-global-assign": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-process-env": "error",
"no-proto": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",
// Variables //
//-----------//
"no-catch-shadow": "error",
"no-label-var": "error",
"no-shadow-restricted-names": "error",
"no-undef-init": "error",
"no-use-before-define": ["error", "nofunc"],
// Stylistic Issues //
//------------------//
"comma-dangle": ["error", "never"],
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "error",
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"consistent-this": ["warn", "self"],
"eol-last": "error",
"func-style": ["error", "declaration"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true
}],
"new-cap": "error",
"new-parens": "error",
"no-array-constructor": "error",
"no-lonely-if": "error",
"no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn",
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-extra-parens": ["error", "functions"],
"quotes": ["error", "single", "avoid-escape"],
"semi": "error",
"semi-spacing": ["error", { "before": false, "after": true }],
"keyword-spacing": "error",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": ["error", "always", { "markers": [","] }],
"wrap-regex": "warn",
// ECMAScript 6 //
//--------------//
"no-var": "off"
},
"parserOptions": {
"sourceType": "module"
}
}
-1
View File
@@ -1 +0,0 @@
Author: Yehuda
-13
View File
@@ -1,13 +0,0 @@
// This test should run with node 0.10 as long as Handlebars has been compiled before
var Handlebars = require('../');
var fs = require('fs');
console.log('Testing build Handlebars with Node version ' + process.version);
var template = fs.readFileSync(require.resolve('./template.txt.hbs'), 'utf-8');
var compiledOutput = Handlebars.compile(template)({author: 'Yehuda'}).trim();
var expectedOutput = fs.readFileSync(require.resolve('./expected.txt'), 'utf-8').trim();
if (compiledOutput !== expectedOutput) {
throw new Error('Compiled output (' + compiledOutput + ') did not match expected output (' + expectedOutput + ')');
}
console.log('Success');
-21
View File
@@ -1,21 +0,0 @@
#!/bin/bash
cd "$( dirname "$( readlink -f "$0" )" )"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# This script tests with precompiler and the built distribution with multiple NodeJS version.
# The rest of the travis-build will only work with newer NodeJS versions, because the build
# tools don't support older versions.
# However, the built distribution should work with older NodeJS versions as well.
# This test is simple by design. It merely ensures, that calling Handlebars does not fail with old versions.
# It does (almost) not test for correctness, because that is already done in the mocha-tests.
# And it does not use any NodeJS based testing framwork to make this part independent of the Node version.
# A list of NodeJS versions is expected as cli-args
echo "Handlebars should be able to run in various versions of NodeJS"
for i in "$@" ; do
nvm install "$i"
nvm exec "$i" node ./run-handlebars.js >/dev/null || exit 1
nvm exec "$i" node ../bin/handlebars template.txt.hbs >/dev/null || exit 1
echo Success
done
-1
View File
@@ -1 +0,0 @@
Author: {{author}}
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
'check-coverage': true,
branches: 100,
lines: 100,
functions: 100,
statements: 100,
exclude: ['**/spec/**'],
reporter: 'html',
};
+20325 -2624
View File
File diff suppressed because it is too large Load Diff
+73 -31
View File
@@ -1,9 +1,8 @@
{
"name": "handlebars",
"barename": "handlebars",
"version": "4.1.2-0",
"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,72 +11,94 @@
],
"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",
"optimist": "^0.6.1",
"source-map": "^0.6.1"
"@handlebars/parser": "^2.1.0",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"yargs": "^16.2.0"
},
"optionalDependencies": {
"peerDependencies": {
"uglify-js": "^3.1.4"
},
"devDependencies": {
"@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",
"benchmark": "~1.0",
"dtslint": "^0.5.5",
"chai": "^4.2.0",
"chai-diff": "^1.0.1",
"concurrently": "^5.0.0",
"dirty-chai": "^2.0.1",
"dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3",
"grunt": "^1.0.3",
"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-eslint": "^20.1.0",
"grunt-saucelabs": "9.x",
"grunt-shell": "^4.0.0",
"grunt-webpack": "^1.0.8",
"istanbul": "^0.3.0",
"jison": "~0.3.0",
"husky": "^3.1.0",
"lint-staged": "^9.5.0",
"mocha": "^5",
"mock-stdin": "^0.3.0",
"mustache": "^2.1.3",
"nyc": "^14.1.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/handlebars.min.js",
"./runtime": "./dist/handlebars.runtime.min.js"
},
"browser": "./dist/cjs/handlebars.js",
"bin": {
"handlebars": "bin/handlebars"
"handlebars": "bin/handlebars.js"
},
"scripts": {
"checkTypes": "dtslint types",
"test": "grunt"
"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:test"
},
"jspm": {
"main": "handlebars",
"directories": {
"lib": "dist/amd"
"lib": "dist/cjs"
},
"buildConfig": {
"minify": true
@@ -86,12 +107,33 @@
"files": [
"bin",
"dist/*.js",
"dist/amd/**/*.js",
"dist/cjs/**/*.js",
"lib",
"print-script",
"release-notes.md",
"runtime.js",
"types/*.d.ts"
]
"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"
}
},
"lint-staged": {
"*.{js,css,json,md}": [
"prettier --write",
"git add"
],
"*.js": [
"eslint --fix",
"git add"
]
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
tabWidth: 2,
semi: true,
singleQuote: true,
trailingComma: 'es5',
};
-95
View File
@@ -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);
}
});
}
+719 -262
View File
File diff suppressed because it is too large Load Diff
-36
View File
@@ -1,36 +0,0 @@
{
"globals": {
"CompilerContext": true,
"Handlebars": true,
"handlebarsEnv": true,
"shouldCompileTo": true,
"shouldCompileToWithPartials": true,
"shouldThrow": 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,
"strictEqual": true,
"define": true
},
"env": {
"mocha": true
},
"rules": {
// Disabling for tests, for now.
"no-path-concat": 0,
"no-var": 0
}
}
+37
View File
@@ -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',
},
};
-105
View File
@@ -1,105 +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/mocha/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="/spec/env/json2.js"></script>
<script src="/spec/env/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>
-125
View File
@@ -1,125 +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/mocha/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="/spec/env/json2.js"></script>
<script src="/spec/env/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>
+6
View File
@@ -0,0 +1,6 @@
{{#someHelper true}}
<div>Some known helper</div>
{{#anotherHelper true}}
<div>Another known helper</div>
{{/anotherHelper}}
{{/someHelper}}
+1
View File
@@ -0,0 +1 @@
<div>This is a test</div>
@@ -0,0 +1 @@
<div>Test Partial</div>
+128 -190
View File
@@ -1,55 +1,113 @@
describe('ast', function() {
describe('ast', function () {
if (!Handlebars.AST) {
return;
}
var AST = Handlebars.AST;
describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
describe('BlockStatement', function () {
it('should throw on mustache mismatch', function () {
shouldThrow(
function () {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
},
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
});
});
describe('helpers', function() {
describe('#helperExpression', function() {
it('should handle mustache statements', function() {
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
describe('helpers', function () {
describe('#helperExpression', function () {
it('should handle mustache statements', function () {
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: undefined,
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [1],
hash: undefined,
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: {},
}),
true
);
});
it('should handle block statements', function() {
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
it('should handle block statements', function () {
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: undefined,
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [1],
hash: undefined,
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: {},
}),
true
);
});
it('should handle subexpressions', function() {
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
it('should handle subexpressions', function () {
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
});
it('should work with non-helper nodes', function() {
equals(AST.helpers.helperExpression({type: 'Program'}), false);
it('should work with non-helper nodes', function () {
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
equals(
AST.helpers.helperExpression({ type: 'PartialStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'ContentStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'CommentStatement' }),
false
);
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
equals(
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
false
);
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'Hash'}), false);
equals(AST.helpers.helperExpression({type: 'HashPair'}), false);
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
});
});
});
describe('Line Numbers', function() {
describe('Line Numbers', function () {
var ast, body;
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
@@ -61,186 +119,66 @@ 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
+ '{{/open}}'); // 11
'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
'{{/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() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
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() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 7, 13, 0);
});
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
});
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', 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, '');
});
it('gets the block helper information correct', function () {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
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);
});
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);
});
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);
});
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 () {
var chainInverseNode = body[7];
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
});
});
+486 -217
View File
@@ -1,305 +1,574 @@
global.handlebarsEnv = null;
beforeEach(function() {
beforeEach(function () {
global.handlebarsEnv = Handlebars.create();
});
describe('basic context', function() {
it('most basic', function() {
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
describe('basic context', function () {
it('most basic', function () {
expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo');
});
it('escaping', function() {
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
it('escaping', function () {
expectTemplate('\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('{{foo}}');
expectTemplate('content \\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('content {{foo}}');
expectTemplate('\\\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('\\food');
expectTemplate('content \\\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('content \\food');
expectTemplate('\\\\ {{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('\\\\ food');
});
it('compiling with a basic context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'It works if all the required keys are provided');
it('compiling with a basic context', function () {
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
.withInput({
cruel: 'cruel',
world: 'world',
})
.withMessage('It works if all the required keys are provided')
.toCompileTo('Goodbye\ncruel\nworld!');
});
it('compiling with a string context', function() {
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
it('compiling with a string context', function () {
expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3');
});
it('compiling with an undefined context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
it('compiling with an undefined context', function () {
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
.withInput(undefined)
.toCompileTo('Goodbye\n\n!');
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
.withInput(undefined)
.toCompileTo('Goodbye');
});
it('comments', function() {
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'comments are ignored');
it('comments', function () {
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
.withInput({
cruel: 'cruel',
world: 'world',
})
.withMessage('comments are ignored')
.toCompileTo('Goodbye\ncruel\nworld!');
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
'blah'
);
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
' blah'
);
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
' blah'
);
});
it('boolean', function() {
it('boolean', function () {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
'booleans show the contents when true');
expectTemplate(string)
.withInput({
goodbye: true,
world: 'world',
})
.withMessage('booleans show the contents when true')
.toCompileTo('GOODBYE cruel world!');
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
'booleans do not show the contents when false');
expectTemplate(string)
.withInput({
goodbye: false,
world: 'world',
})
.withMessage('booleans do not show the contents when false')
.toCompileTo('cruel world!');
});
it('zeros', function() {
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
'num1: 42, num2: 0');
shouldCompileTo('num: {{.}}', 0, 'num: 0');
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
it('zeros', function () {
expectTemplate('num1: {{num1}}, num2: {{num2}}')
.withInput({
num1: 42,
num2: 0,
})
.toCompileTo('num1: 42, num2: 0');
expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0');
expectTemplate('num: {{num1/num2}}')
.withInput({ num1: { num2: 0 } })
.toCompileTo('num: 0');
});
it('false', function() {
it('false', function () {
/* eslint-disable no-new-wrappers */
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
shouldCompileTo('val: {{.}}', false, 'val: false');
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
expectTemplate('val1: {{val1}}, val2: {{val2}}')
.withInput({
val1: false,
val2: new Boolean(false),
})
.toCompileTo('val1: false, val2: false');
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false');
expectTemplate('val: {{val1/val2}}')
.withInput({ val1: { val2: false } })
.toCompileTo('val: false');
expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
.withInput({
val1: false,
val2: new Boolean(false),
})
.toCompileTo('val1: false, val2: false');
expectTemplate('val: {{{val1/val2}}}')
.withInput({ val1: { val2: false } })
.toCompileTo('val: false');
/* eslint-enable */
});
it('should handle undefined and null', function() {
shouldCompileTo('{{awesome undefined null}}',
{
awesome: function(_undefined, _null, options) {
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
}
it('should handle undefined and null', function () {
expectTemplate('{{awesome undefined null}}')
.withInput({
awesome: function (_undefined, _null, options) {
return (
(_undefined === undefined) +
' ' +
(_null === null) +
' ' +
typeof options
);
},
'true true object');
shouldCompileTo('{{undefined}}',
{
'undefined': function() {
return 'undefined!';
}
})
.toCompileTo('true true object');
expectTemplate('{{undefined}}')
.withInput({
undefined: function () {
return 'undefined!';
},
'undefined!');
shouldCompileTo('{{null}}',
{
'null': function() {
return 'null!';
}
})
.toCompileTo('undefined!');
expectTemplate('{{null}}')
.withInput({
null: function () {
return 'null!';
},
'null!');
})
.toCompileTo('null!');
});
it('newlines', function() {
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
it('newlines', function () {
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
});
it('escaping text', function() {
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
it('escaping text', function () {
expectTemplate("Awesome's")
.withMessage(
"text is escaped so that it doesn't get caught on single quotes"
)
.toCompileTo("Awesome's");
expectTemplate('Awesome\\')
.withMessage("text is escaped so that the closing quote can't be ignored")
.toCompileTo('Awesome\\');
expectTemplate('Awesome\\\\ foo')
.withMessage("text is escaped so that it doesn't mess up backslashes")
.toCompileTo('Awesome\\\\ foo');
expectTemplate('Awesome {{foo}}')
.withInput({ foo: '\\' })
.withMessage("text is escaped so that it doesn't mess up backslashes")
.toCompileTo('Awesome \\');
expectTemplate(" ' ' ")
.withMessage('double quotes never produce invalid javascript')
.toCompileTo(" ' ' ");
});
it('escaping expressions', function() {
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with 3 handlebars aren't escaped");
it('escaping expressions', function () {
expectTemplate('{{{awesome}}}')
.withInput({ awesome: "&'\\<>" })
.withMessage("expressions with 3 handlebars aren't escaped")
.toCompileTo("&'\\<>");
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with {{& handlebars aren't escaped");
expectTemplate('{{&awesome}}')
.withInput({ awesome: "&'\\<>" })
.withMessage("expressions with {{& handlebars aren't escaped")
.toCompileTo("&'\\<>");
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped');
expectTemplate('{{awesome}}')
.withInput({ awesome: '&"\'`\\<>' })
.withMessage('by default expressions should be escaped')
.toCompileTo('&amp;&quot;&#x27;&#x60;\\&lt;&gt;');
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: &lt;b&gt;'}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'escaping should properly handle amperstands');
expectTemplate('{{awesome}}')
.withInput({ awesome: 'Escaped, <b> looks like: &lt;b&gt;' })
.withMessage('escaping should properly handle amperstands')
.toCompileTo('Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;');
});
it("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
"functions returning safestrings aren't escaped");
it("functions returning safestrings shouldn't be escaped", function () {
expectTemplate('{{awesome}}')
.withInput({
awesome: function () {
return new Handlebars.SafeString("&'\\<>");
},
})
.withMessage("functions returning safestrings aren't escaped")
.toCompileTo("&'\\<>");
});
it('functions', function() {
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
'functions are called and render their output');
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
'functions are bound to the context');
it('functions', function () {
expectTemplate('{{awesome}}')
.withInput({
awesome: function () {
return 'Awesome';
},
})
.withMessage('functions are called and render their output')
.toCompileTo('Awesome');
expectTemplate('{{awesome}}')
.withInput({
awesome: function () {
return this.more;
},
more: 'More awesome',
})
.withMessage('functions are bound to the context')
.toCompileTo('More awesome');
});
it('functions with context argument', function() {
shouldCompileTo('{{awesome frank}}',
{awesome: function(context) { return context; },
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
});
it('pathed functions with context argument', function() {
shouldCompileTo('{{bar.awesome frank}}',
{bar: {awesome: function(context) { return context; }},
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
});
it('depthed functions with context argument', function() {
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
{awesome: function(context) { return context; },
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
it('functions with context argument', function () {
expectTemplate('{{awesome frank}}')
.withInput({
awesome: function (context) {
return context;
},
frank: 'Frank',
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('block functions with context argument', function() {
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
{awesome: function(context, options) { return options.fn(context); }},
'inner 1', 'block functions are called with context and options');
it('pathed functions with context argument', function () {
expectTemplate('{{bar.awesome frank}}')
.withInput({
bar: {
awesome: function (context) {
return context;
},
},
frank: 'Frank',
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('depthed block functions with context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
{value: true, awesome: function(context, options) { return options.fn(context); }},
'inner 1', 'block functions are called with context and options');
it('depthed functions with context argument', function () {
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
.withInput({
awesome: function (context) {
return context;
},
frank: 'Frank',
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('block functions without context argument', function() {
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
{awesome: function(options) { return options.fn(this); }},
'inner', 'block functions are called with options');
});
it('pathed block functions without context argument', function() {
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
{foo: {awesome: function() { return this; }}},
'inner', 'block functions are called with options');
});
it('depthed block functions without context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
{value: true, awesome: function() { return this; }},
'inner', 'block functions are called with options');
it('block functions with context argument', function () {
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
.withInput({
awesome: function (context, options) {
return options.fn(context);
},
})
.withMessage('block functions are called with context and options')
.toCompileTo('inner 1');
});
it('paths with hyphens', function() {
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
it('depthed block functions with context argument', function () {
expectTemplate(
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
)
.withInput({
value: true,
awesome: function (context, options) {
return options.fn(context);
},
})
.withMessage('block functions are called with context and options')
.toCompileTo('inner 1');
});
it('nested paths', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Nested paths access nested objects');
it('block functions without context argument', function () {
expectTemplate('{{#awesome}}inner{{/awesome}}')
.withInput({
awesome: function (options) {
return options.fn(this);
},
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('nested paths with empty string value', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
'Goodbye world!', 'Nested paths access nested objects with empty string');
it('pathed block functions without context argument', function () {
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
.withInput({
foo: {
awesome: function () {
return this;
},
},
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('literal paths', function() {
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Literal paths can be used');
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Literal paths can be used');
it('depthed block functions without context argument', function () {
expectTemplate(
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
)
.withInput({
value: true,
awesome: function () {
return this;
},
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('literal references', function() {
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo\'bar"}} world!', {"foo'bar": 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
it('paths with hyphens', function () {
expectTemplate('{{foo-bar}}')
.withInput({ 'foo-bar': 'baz' })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
expectTemplate('{{foo.foo-bar}}')
.withInput({ foo: { 'foo-bar': 'baz' } })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
expectTemplate('{{foo/foo-bar}}')
.withInput({ foo: { 'foo-bar': 'baz' } })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
});
it("that current context path ({{.}}) doesn't hit helpers", function() {
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
it('nested paths', function () {
expectTemplate('Goodbye {{alan/expression}} world!')
.withInput({ alan: { expression: 'beautiful' } })
.withMessage('Nested paths access nested objects')
.toCompileTo('Goodbye beautiful world!');
});
it('complex but empty paths', function() {
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
shouldCompileTo('{{person/name}}', {person: {}}, '');
it('nested paths with empty string value', function () {
expectTemplate('Goodbye {{alan/expression}} world!')
.withInput({ alan: { expression: '' } })
.withMessage('Nested paths access nested objects with empty string')
.toCompileTo('Goodbye world!');
});
it('this keyword in paths', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
'This keyword in paths evaluates to current context');
it('literal paths', function () {
expectTemplate('Goodbye {{[@alan]/expression}} world!')
.withInput({ '@alan': { expression: 'beautiful' } })
.withMessage('Literal paths can be used')
.toCompileTo('Goodbye beautiful world!');
string = '{{#hellos}}{{this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
expectTemplate('Goodbye {{[foo bar]/expression}} world!')
.withInput({ 'foo bar': { expression: 'beautiful' } })
.withMessage('Literal paths can be used')
.toCompileTo('Goodbye beautiful world!');
});
it('this keyword nested inside path', function() {
shouldThrow(function() {
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
}, Error, 'Invalid path: text/this - 1:13');
it('literal references', function () {
expectTemplate('Goodbye {{[foo bar]}} world!')
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
expectTemplate('Goodbye {{"foo bar"}} world!')
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate("Goodbye {{'foo bar'}} world!")
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{"foo[bar"}} world!')
.withInput({ 'foo[bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{"foo\'bar"}} world!')
.withInput({ "foo'bar": 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate("Goodbye {{'foo\"bar'}} world!")
.withInput({ 'foo"bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
});
it('this keyword in helpers', function() {
var helpers = {foo: function(value) {
return 'bar ' + value;
}};
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
'This keyword in paths evaluates to current context');
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
it("that current context path ({{.}}) doesn't hit helpers", function () {
expectTemplate('test: {{.}}')
.withInput(null)
.withHelpers({ helper: 'awesome' })
.toCompileTo('test: ');
});
it('this keyword nested inside helpers param', function() {
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, 'Invalid path: text/this - 1:17');
it('complex but empty paths', function () {
expectTemplate('{{person/name}}')
.withInput({ person: { name: null } })
.toCompileTo('');
shouldCompileTo(
'{{foo [this]}}',
{foo: function(value) { return value; }, 'this': 'bar'},
'bar');
shouldCompileTo(
'{{foo text/[this]}}',
{foo: function(value) { return value; }, text: {'this': 'bar'}},
'bar');
expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo('');
});
it('pass string literals', function() {
shouldCompileTo('{{"foo"}}', {}, '');
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
it('this keyword in paths', function () {
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
.withMessage('This keyword in paths evaluates to current context')
.toCompileTo('goodbyeGoodbyeGOODBYE');
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
.withInput({
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }],
})
.withMessage('This keyword evaluates in more complex paths')
.toCompileTo('helloHelloHELLO');
});
it('pass number literals', function() {
shouldCompileTo('{{12}}', {}, '');
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
shouldCompileTo('{{12.34}}', {}, '');
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
it('this keyword nested inside path', function () {
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
Error,
'Invalid path: text/this - 1:13'
);
expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar');
expectTemplate('{{text/[this]}}')
.withInput({ text: { this: 'bar' } })
.toCompileTo('bar');
});
it('pass boolean literals', function() {
shouldCompileTo('{{true}}', {}, '');
shouldCompileTo('{{true}}', { '': 'foo' }, '');
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
});
it('should handle literals in subexpression', function() {
it('this keyword in helpers', function () {
var helpers = {
foo: function(arg) {
return arg;
}
foo: function (value) {
return 'bar ' + value;
},
};
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
.withHelpers(helpers)
.withMessage('This keyword in paths evaluates to current context')
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
.withInput({
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }],
})
.withHelpers(helpers)
.withMessage('This keyword evaluates in more complex paths')
.toCompileTo('bar hellobar Hellobar HELLO');
});
it('this keyword nested inside helpers param', function () {
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
Error,
'Invalid path: text/this - 1:17'
);
expectTemplate('{{foo [this]}}')
.withInput({
foo: function (value) {
return value;
},
this: 'bar',
})
.toCompileTo('bar');
expectTemplate('{{foo text/[this]}}')
.withInput({
foo: function (value) {
return value;
},
text: { this: 'bar' },
})
.toCompileTo('bar');
});
it('pass string literals', function () {
expectTemplate('{{"foo"}}').toCompileTo('');
expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar');
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
.withInput({
foo: ['bar', 'baz'],
})
.toCompileTo('barbaz');
});
it('pass number literals', function () {
expectTemplate('{{12}}').toCompileTo('');
expectTemplate('{{12}}').withInput({ 12: 'bar' }).toCompileTo('bar');
expectTemplate('{{12.34}}').toCompileTo('');
expectTemplate('{{12.34}}').withInput({ 12.34: 'bar' }).toCompileTo('bar');
expectTemplate('{{12.34 1}}')
.withInput({
12.34: function (arg) {
return 'bar' + arg;
},
})
.toCompileTo('bar1');
});
it('pass boolean literals', function () {
expectTemplate('{{true}}').toCompileTo('');
expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo('');
expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo');
});
it('should handle literals in subexpression', function () {
expectTemplate('{{foo (false)}}')
.withInput({
false: function () {
return 'bar';
},
})
.withHelper('foo', function (arg) {
return arg;
})
.toCompileTo('bar');
});
});
+352 -243
View File
@@ -1,316 +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';
});
@@ -319,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);
@@ -334,13 +431,25 @@ describe('blocks', function() {
equals(handlebarsEnv.decorators.foo, undefined);
equals(handlebarsEnv.decorators.bar, undefined);
});
it('fails with multiple and args', function() {
shouldThrow(function() {
handlebarsEnv.registerDecorator({
world: function() { return 'world!'; },
testHelper: function() { return 'found it!'; }
}, {});
}, Error, 'Arg not supported with multiple decorators');
it('fails with multiple and args', function () {
shouldThrow(
function () {
handlebarsEnv.registerDecorator(
{
world: function () {
return 'world!';
},
testHelper: function () {
return 'found it!';
},
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
});
});
});
+591 -265
View File
File diff suppressed because it is too large Load Diff
+146 -52
View File
@@ -1,49 +1,96 @@
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);
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
equal(
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
compile('{{foo.bar baz "foo" true false bat=1}}')
),
true
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (baz bat=1)}}')
),
true
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
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);
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
equal(
compile('{{foo.bar baz bat=1}}').equals(
compile('{{foo.bar bar bat=1}}')
),
false
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (bar bat=1)}}')
),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(
compile('{{#foo}} {{foo}}{{/foo}}')
),
false
);
});
});
describe('#compile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.compile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
shouldThrow(function() {
Handlebars.compile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
describe('#compile', function () {
it('should fail with invalid input', function () {
shouldThrow(
function () {
Handlebars.compile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function () {
Handlebars.compile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
});
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(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
equal(
err.message,
"if doesn't match def - 2:5",
'Checking error message'
);
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
@@ -54,57 +101,104 @@ 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(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(err.propertyIsEnumerable('column'), true, 'Checking error column');
equal(
Object.prototype.propertyIsEnumerable.call(err, 'column'),
true,
'Checking error column'
);
}
});
it('can utilize AST instance', function() {
equal(Handlebars.compile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})(), 'Hello');
it('can utilize AST instance', function () {
equal(
Handlebars.compile({
type: 'Program',
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() {
var options = {data: [{a: 'foo'}, {a: 'bar'}]};
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
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.knownHelpers property(GH-1327)', function() {
var options = {knownHelpers: {}};
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(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
);
});
it('should not modify the options.knownHelpers property(GH-1327)', function () {
var options = { knownHelpers: {} };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ knownHelpers: {} }, 0, 2)
);
});
});
describe('#precompile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.precompile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
shouldThrow(function() {
Handlebars.precompile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
describe('#precompile', function () {
it('should fail with invalid input', function () {
shouldThrow(
function () {
Handlebars.precompile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function () {
Handlebars.precompile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
});
it('can utilize AST instance', function() {
equal(/return "Hello"/.test(Handlebars.precompile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})), true);
it('can utilize AST instance', function () {
equal(
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program',
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);
});
});
+205 -198
View File
@@ -1,29 +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) {
@@ -34,238 +32,247 @@ 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({}, { data: { 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);
it('data can be functions', function () {
expectTemplate('{{@hello}}')
.withRuntimeOptions({
data: {
hello: function () {
return 'hello';
},
},
})
.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) {
frame[prop] = options.hash[prop];
}
}
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');
return options.fn(this, { data: frame });
})
.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) {
return options.fn({exclaim: '?'});
},
world: function(thing, 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: '?' });
})
.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) {
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
},
world: function(thing, 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: '?' });
})
.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) {
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
},
world: function(thing, 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' } }
);
})
.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) {
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
},
world: function(thing, 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' } });
})
.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('{{#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});
}
},
data: {
depth: 0
}
});
equals('2 1 0', result);
describe('nesting', function () {
it('the root context can be looked up via @root', function () {
expectTemplate(
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
)
.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,
},
})
.toCompileTo('2 1 0');
});
});
});

Some files were not shown because too many files have changed in this diff Show More