Compare commits

...

514 Commits

Author SHA1 Message Date
Nils Knappmeier f691db546e v4.1.1 2019-03-16 22:28:38 +01:00
Nils Knappmeier 25b2e11dd9 Update release notes 2019-03-16 22:28:03 +01:00
Nils Knappmeier e5c39375ac Update release notes 2019-03-16 22:24:34 +01:00
Nils Knappmeier aef72870b6 Merge pull request #1511 from wycats/saucelabs
Fix Saucelabs tests
2019-03-15 23:16:23 +01:00
Nils Knappmeier 684f1032c6 chore: reactivate saucelabs-tests 2019-03-15 23:01:49 +01:00
Nils Knappmeier 7840ab66a5 test: make security testcase internet explorer compatible
Internet Explorer does not support the 'class Testclass {}' notation,
and tests are not compiled using babel.

closes #1497
2019-03-15 23:01:49 +01:00
Nils Knappmeier 4108b8330b Merge pull request #1504 from liqiang372/deprecate-substr-method
deprecate substr method and use existing strip function in grammar
2019-03-14 20:23:58 +01:00
Qiang Li 445ae12fa4 deprecate substr method and use existing strip function in grammar 2019-03-13 14:24:19 -07:00
Nils Knappmeier 5cedd62d7b fix: add "runtime.d.ts" to allow "require('handlebars/runtime')"
see #1499
2019-02-22 00:30:51 +01:00
Nils Knappmeier 40fb115e53 Revert "chore: re-activate saucelabs"
This reverts commit b2e2cfe56d.
2019-02-19 10:49:24 +01:00
Nils Knappmeier b2e2cfe56d chore: re-activate saucelabs 2019-02-19 10:31:42 +01:00
Nils Knappmeier 037bfbf4d9 Merge pull request #1500 from wycats/neo-async
Use `neo-async` instead of `async
2019-02-19 09:49:49 +01:00
Nils Knappmeier 048f2ce7d2 refactor: replace "async" with "neo-async"
The main reason is that neo-async takes a lot less space due to the missing lodash-dependency.
The other is speed.

closes #1431
2019-02-18 19:26:38 +01:00
Nils Knappmeier b92589a3b0 test: add test for NodeJS compatibility
The test is a simple addition to the existing tests. It should ensure
that the built Handlebars artifact only uses language features that are
available in old versions of NodeJS. A simple program and the
precompiler are started with NodeJS 0.10 to 11
2019-02-18 19:20:39 +01:00
Nils Knappmeier 1c62d4c5d0 Merge branch 'issue-1495' into 4.x 2019-02-07 11:03:35 +01:00
Nils Knappmeier 7caca944b1 v4.1.0 2019-02-07 10:46:32 +01:00
Nils Knappmeier 7bd34fb466 Update release notes 2019-02-07 10:46:01 +01:00
Nils Knappmeier b02e9a25ee test: run appveyor tests in Node 10 2019-02-07 10:19:53 +01:00
Nils Knappmeier f1c8b2e2a2 chore: disable sauce-labs
Related to #1497
2019-02-07 10:19:53 +01:00
Nils Knappmeier dbc50ac705 chore: bump version of grunt-saucelabs 2019-02-07 10:19:53 +01:00
Nils Knappmeier c6a8fc1c04 chore: add .idea and yarn-error.log to .gitignore 2019-02-07 10:19:53 +01:00
Nils Knappmeier 42841c41a4 fix: disallow access to the constructor in templates to prevent RCE
This commit fixes a Remote Code Execution (RCE) reported by
npm-security. Access to non-enumerable "constructor"-properties
is now prohibited by the compiled template-code, because this
the first step on the way to creating and execution arbitrary
JavaScript code.
The vulnerability affects systems where an attacker is allowed to
inject templates into the Handlebars setup.
Further details of the attack may be disclosed by npm-security.

Closes #1267
Closes #1495
2019-02-07 10:19:53 +01:00
Nils Knappmeier 56fc6768d1 test: run appveyor tests in Node 10 2019-02-07 10:14:44 +01:00
Nils Knappmeier ee3022228b chore: disable sauce-labs
Related to #1497
2019-02-07 10:04:17 +01:00
Nils Knappmeier 05e6293bb3 chore: bump version of grunt-saucelabs 2019-02-07 09:53:09 +01:00
Nils Knappmeier 2db0d123c8 chore: add .idea and yarn-error.log to .gitignore 2019-02-07 08:49:52 +01:00
Nils Knappmeier edc6220d51 fix: disallow access to the constructor in templates to prevent RCE
This commit fixes a Remote Code Execution (RCE) reported by
npm-security. Access to non-enumerable "constructor"-properties
is now prohibited by the compiled template-code, because this
the first step on the way to creating and execution arbitrary
JavaScript code.
The vulnerability affects systems where an attacker is allowed to
inject templates into the Handlebars setup.
Further details of the attack may be disclosed by npm-security.

Closes #1267
Closes #1495
2019-02-07 08:49:41 +01:00
Nils Knappmeier bacd473fe6 chore: fix components/handlebars package.json and auto-update on release 2019-01-02 01:07:05 +01:00
Timothy Lindvall 27ac1ee396 Feat: Import TypeScript typings
- Import typings from DefinitelyTyped into repo.
- Update typings header to cite contributors from history and git blame.
- Update package.json to add typings field.
2018-12-21 23:27:58 +01:00
Nils Knappmeier 78dd89c13a chore: Use node 10 to build handlebars
Node 10 is LTS now...
2018-12-15 23:40:08 +01:00
Nils Knappmeier 6b87c21fc4 chore/doc: Add more release docs 2018-12-15 23:37:40 +01:00
Nils Knappmeier 8d22e6f501 v4.0.12 2018-09-04 20:44:38 +02:00
Nils Knappmeier 3c970cc9c1 Update release notes 2018-09-04 20:44:07 +02:00
Nils Knappmeier abba3c7526 Update release notes 2018-09-04 20:37:53 +02:00
Nils Knappmeier 4bf1c4ff66 Update release notes 2018-09-04 20:36:06 +02:00
Nils Knappmeier 41b6a11d11 Merge branch '4.x' of github.com:wycats/handlebars.js into 4.x 2018-09-04 20:08:57 +02:00
Nils Knappmeier 2d28f920b0 bump grunt-plugin-dependencies to 1.x versions 2018-09-04 18:53:01 +02:00
Nils Knappmeier 29b174468d style: omit linting error caused by removing "if" 2018-09-04 18:53:01 +02:00
Nils Knappmeier d130ed2bc1 chore: bump various dependencies
- grunt -> 1
- grunt-contrib-watch -> 1
- mocha -> 5
  - in common.js: define "global" , see mochajs/mocha#1159
  - in builtins.js: revert "console.log" to old value just
    after using the mock (in log-helper tests), because
    mocha calls "console.log" on failure, before
    "afterEach"
2018-09-04 18:53:01 +02:00
Nils Knappmeier 2145c14994 bump grunt-plugin-dependencies to 1.x versions 2018-09-04 00:18:46 +02:00
Nils Knappmeier 8359722e5d style: omit linting error caused by removing "if" 2018-09-04 00:16:01 +02:00
Nils Knappmeier a1d864d4a7 chore: bump various dependencies
- grunt -> 1
- grunt-contrib-watch -> 1
- mocha -> 5
  - in common.js: define "global" , see mochajs/mocha#1159
  - in builtins.js: revert "console.log" to old value just
    after using the mock (in log-helper tests), because
    mocha calls "console.log" on failure, before
    "afterEach"
2018-09-04 00:15:02 +02:00
Qiang Li 0ddff8b388 unnecessary check
(cherry picked from commit e1fa310)
2018-05-31 23:16:26 +02:00
Nils Knappmeier 288e986161 Docs: Document branches in the CONTRIBUTING guide 2018-05-31 23:15:24 +02:00
Nils Knappmeier 30df8a1ac7 Testcase for accessing @root from a partial-block
related to #1445
2018-05-31 23:12:08 +02:00
Nils Knappmeier cda544bca9 Add package.json to components shim
This is an attempt to provide a valid package.json-file to the shim
repository for bower, in order to support `bower-away`

see components/handlebars.js#24
2017-11-28 22:13:17 +01:00
aaharu 69c6ca528d Use files field 2017-11-26 02:08:20 +09:00
Nils Knappmeier a4e39bdfd0 Fix release-notes (links to contributors` pages) 2017-11-09 10:46:39 +01:00
Nils Knappmeier b86b9189fa Fix release-notes (links to github-repo) 2017-11-09 10:44:20 +01:00
tim d3d39423a3 upgrade uglify-js 2017-10-21 23:04:23 +02:00
Nils Knappmeier 73d5637564 Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 2017-10-21 16:09:33 +02:00
Nils Knappmeier 7729aa956b Update grunt-eslint to 20.1.0 2017-10-21 15:42:27 +02:00
Nils Knappmeier 8947dd077c Update jsfiddle to 4.0.11 2017-10-17 23:15:53 +02:00
Nils Knappmeier 1e954ddf3c v4.0.11 2017-10-17 22:52:25 +02:00
Nils Knappmeier 1ac131e652 Update release notes 2017-10-17 22:51:42 +02:00
Nils Knappmeier 59548b4bdc Extend compiler-api example by replacing child-compiler
closes #1376

(cherry picked from commit ce3cd8a)
2017-10-17 22:30:31 +02:00
Marcos Marado 21386b6474 Update (C) year in the LICENSE file
Welcome to 2017!

(cherry picked from commit 33773c2)
2017-10-17 22:21:41 +02:00
Nils Knappmeier 79309659e1 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.

(cherry picked from commit d5caa56)
2017-10-17 22:18:56 +02:00
Nils Knappmeier 5b76f041b3 Fix build on Windows
Closes #1233

- Handle path-separators properly. Use "path.sep" instead of "/".
  Or use "require.resolve()" if possible
- Use "execFile" instead of "exec" to run the Handlebars executable.
  This prevents problems due to (missing) shell escaping.
- Use explicit call to "node" in order to run the executable on Windows.
- Add "appveyor"-CI in order to run regular tests on Windows.
2017-08-23 22:29:30 +02:00
Nils Knappmeier 1ed163f4ae Update jsfiddle-link to 4.0.10 2017-05-21 14:15:39 +02:00
Nils Knappmeier 670ec6fafb v4.0.10 2017-05-21 14:11:27 +02:00
Nils Knappmeier 2e935df8bf Update release notes 2017-05-21 14:10:50 +02:00
Nils Knappmeier 0e953d1db5 Replace "Object.assign" (not support in IE) by "util/extend" 2017-05-21 14:02:35 +02:00
Nils Knappmeier 5ec78a8c70 v4.0.9 2017-05-21 13:39:05 +02:00
Nils Knappmeier 5333f316c3 Update release notes 2017-05-21 13:38:28 +02:00
Nils Knappmeier 8a836e2272 Handlebars.compile() does not modify "options" anymore
Fixes #1327

- This commit creates a shallow copy of the "options" passed to
  Handlebars.compile() in order to prevent modifications
- Note that "new Handlebars.Compiler().compile(..., options)" still
  modify the options object. This might change in the future, if
  anybody needs a fix for that.
2017-05-21 13:20:48 +02:00
Luiz Américo cc554a5813 Fix build in windows
(cherry picked from commit 275ab37)
2017-05-15 00:11:59 +02:00
Nils Knappmeier ed879a6068 Ensure LF line-edings in handlebars-template fixtures (*.hbs)
Fixes #1331
2017-05-14 23:57:07 +02:00
Nils Knappmeier 2e21e2bc9a Run integration test with node handlebars -a ... on Windows
Fixes #1233

NodeJS files cannot be executed directly on Windows.
2017-05-14 23:37:24 +02:00
Nils Knappmeier bdfdbea09c Ensure LF line-edings in lexer-files (*.l)
Related to #1233
2017-05-14 22:49:19 +02:00
Nils Knappmeier b50ef03823 Force LF line-endings for spec/artifacts
Fixes #1331
2017-05-14 14:43:14 +02:00
Nils Knappmeier 6e6269fcd7 Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul
Fixes #1331

Due to the way, "bin"-files are distributed into the node_modules/.bin
directory on Windows, the task "test:cov" did not work on Windows.
This commit uses the node-script directly.
2017-05-14 14:43:14 +02:00
Nils Knappmeier 7378f854c3 Publish valid semver task independently of the branch 2017-05-13 00:57:46 +02:00
Nils Knappmeier fed5818876 v4.0.8 2017-05-02 22:55:44 +02:00
Nils Knappmeier 0e81f0a082 Update release notes 2017-05-02 22:55:08 +02:00
Nils Knappmeier a00c598266 Allow partial-blocks to be executed without "options"
Closes #1341

If the @partial-block is called as parameter of a helper (like in
{{#if @partial-block}}...{{/if}}, the partialBlockWrapper is executed
without "options"-parameter. It should still work in without an error
in such a case.
2017-05-02 22:48:15 +02:00
Nils Knappmeier 606fa55b0a v4.0.7 2017-04-29 22:52:09 +02:00
Nils Knappmeier 8e09f0ee4e Update release-notes for 4.0.7 2017-04-29 22:47:39 +02:00
Nils Knappmeier c8f4b570c1 Fix context-stack when calling block-helpers on null values
Fixes #1319

Original behaviour:
- When a block-helper was called on a null-context, an empty object was used
  as context instead. (#1093)
- The runtime verifies that whether the current context equals the
  last context and adds the current context to the stack, if it is not.
  This is done, so that inside a block-helper, the ".." path can be used
  to go back to the parent element.
- If the helper is called on a "null" element, the context was added, even
  though it shouldn't be, because the "null != {}"

Fix:
- The commit replaces "null" by the identifiable "container.nullContext"
  instead of "{}". "nullContext" is a sealed empty object.
- An additional check in the runtime verifies that the context is
  only added to the stack, if it is not the nullContext.

Backwards compatibility within 4.0.x-versions:
- This commit changes the compiler and compiled templates would not work
  with runtime-versions 4.0.0 - 4.0.6, because of the "nullContext"
  property. That's way, the compiled code reads
  "(container.nullContext || {})" so that the behavior will degrade
  gracefully with older runtime versions: Everything else will work
  fine, but GH-1319 will still be broken, if you use a newer compiler
  with a pre 4.0.7 runtime.
2017-03-25 15:00:58 +01:00
Tobias Bieniek b617375219 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

(cherry picked from commit 508347e)
2017-02-24 08:35:48 +01:00
Nils Knappmeier 63a8e0caa2 Add more tests for partial-blocks and inline partials
- Multiple partial-blocks at different nesting levels
- Calling partial-blocks twice with nested partial-blocks
- Calling the partial-block from within the #each-helper
- nested inline partials with partial-blocks on different nesting levels
- nested inline partials (twice at each level)
2017-02-14 22:18:30 +01:00
Nils Knappmeier 5a164d0ca5 Fix for #1252: Using @partial-block twice in a template not possible
Fixes #1252
- This fix treats partial-blocks more like closures and uses the closure-context of
  the "invokePartial"-function to store the @partial-block for the partial.
- Adds a tes for the fix
2017-02-14 22:18:30 +01:00
Joonas Lahtinen 01b0f656bb Avoid duplicate "sourceMappingURL=" lines.
Avoid duplicate // sourceMappingURL=... lines when minifying AND
generating a map. UglifyJS2 will write the line when minifying.

(cherry picked from commit 660a117)
2017-02-14 22:01:13 +01:00
Travis Nelson 406f2ee01f require('sys') is deprecated, using 'util' instead
(node:30288) DeprecationWarning: sys is deprecated. Use util instead.

(cherry picked from commit 9a36966 by @travnels)
2017-01-01 20:47:59 +01:00
Nils Knappmeier a023cb4dd9 Make "column"-property of Errors enumerable
Fixes #1284

Appearently, there is a use-case of stringifying the error in order to
evaluated its properties on another system. There was a regression
from 4.0.5  to 4.0.6 that the column-property of compilation errors
was not  enumerable anymore in 4.0.6 (due to  commit 20c965c) and
thus was not included in the output of "JSON.stringify".
2016-12-30 16:33:32 +01:00
Nils Knappmeier c7dc353956 Testcase to verify that compile-errors have a column-property
Related to #1284

The test ensures that the property is there, because it is important to
some people.
2016-12-30 16:33:32 +01:00
Lon Ingram ad3037cf54 v4.0.6 (again)
Missed some versions in the repo that needed bumping.
2016-11-12 12:32:45 -06:00
Lon Ingram 44ce5f40aa Update release notes 2016-11-12 10:40:57 -06:00
Lon Ingram 3385de0921 v4.0.6 2016-11-11 12:04:25 -06:00
Lon Ingram abc160ae8e Use XML-like tags in test instead of bizarre dot delimiters 2016-11-11 12:01:16 -06:00
Lon Ingram 5ee91cb58a Add test reproducing #1185 2016-11-11 12:01:16 -06:00
Lon Ingram ef9e0dc2ab 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-11-11 12:01:16 -06:00
Kabir 0d0c4d1bbd 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-11-11 12:01:16 -06:00
Charles O'Farrell 8ff49cef52 Ensure that existing blockParams and depths are respected on dupe programs
Fixes #1186
2016-11-11 12:01:16 -06:00
Mr. Leo 1b885d496a Added cory 2016-11-11 12:01:16 -06:00
Gennadiy Litvinyuk 45e14b7b45 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-11-11 12:01:16 -06:00
Paul Falgout 1379e8f50c Contributing doc fix: failing thats -> failing tests 2016-11-11 12:01:16 -06:00
Tim Wang 6272c15074 Update license date 2016-11-11 12:01:16 -06:00
kpdecker 959ee557b0 Update jsfiddle to point to latest 2016-11-11 12:01:16 -06:00
Anders D. Johnson 577b760b50 Fix typos on decorators-api.md. 2016-11-11 12:01:16 -06:00
kpdecker 8c19874497 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.
2016-11-11 12:01:16 -06:00
Ryan Lewis 8fc16367b0 Add documentation for running tests to contributing.md
lawnsea: this contains the changes from 8a9c79b
2016-11-11 12:01:16 -06:00
kpdecker 400916c225 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.
2016-11-11 12:01:16 -06:00
kpdecker fee23342b1 Update target browser test versions 2016-11-11 12:01:16 -06:00
kpdecker 32d6363841 Exclude coverage check in exception conditional 2016-11-11 12:01:16 -06:00
kpdecker 20c965cd65 Fix throw when creating exception object in Safari
https://github.com/jquery/esprima/issues/1290
2016-11-11 12:01:16 -06:00
kpdecker 6c9f98c025 Update build for modern node versions 2016-11-11 12:01:16 -06:00
kpdecker c393c81561 Relax depth check for context push
Fixes #1135
2016-11-11 12:01:16 -06:00
kpdecker 205c61cfb1 v4.0.5 2015-11-19 23:06:54 -06:00
kpdecker ddaff8ea29 Update release notes 2015-11-19 23:06:29 -06:00
kpdecker 685cf92bcb Return current handlebars instance from noConflict
Fixes wycats/handlebars-site#131
2015-11-19 22:58:26 -06:00
kpdecker 7a6c228592 Add webpack to dev dependency to support npm 3 2015-11-19 22:55:03 -06:00
kpdecker 0a3b3c2831 Further relax uglify dependency 2015-11-19 22:54:39 -06:00
kpdecker 251ec3b128 Work around uglify preserveComments some issue
Root cause: https://github.com/gruntjs/grunt-contrib-uglify/issues/366

Fixes #1129
2015-11-19 22:54:20 -06:00
kpdecker c21118d04b Include tests for minimized artifacts 2015-11-19 22:53:31 -06:00
Kevin Decker 0f00f31f6b Merge pull request #1132 from plynchnlm/master
Update uglify-js to avoid vulnerability
2015-11-18 01:13:47 -06:00
Paul Lynch 1dc417f1a5 Update uglify-js to avoid vulnerability 2015-11-16 18:23:25 -05:00
kpdecker 9f59de9657 Fix lint errors under latest eslint 2015-10-31 13:32:43 -05:00
Kevin Decker 3406b95cfd Merge pull request #1127 from victorwpbastos/master
Tag DOMBars as deprecated
2015-10-31 12:25:38 -05:00
Victor Bastos 7b48a284e0 Tag DOMBars as deprecated 2015-10-31 13:51:14 -02:00
kpdecker 98a6717e9e Add print-script helper script 2015-10-30 12:21:15 -05:00
kpdecker b7c95e9feb v4.0.4 2015-10-29 01:54:17 -05:00
kpdecker b1e561ee26 Update release notes 2015-10-29 01:53:40 -05:00
Kevin Decker 690ae2edc7 Merge pull request #1125 from nknapp/patch-2
Add promised-handlebars to "in-the-wild"-list
2015-10-29 01:50:17 -05:00
Nils Knappmeier 496ed8a25f Add promised-handlebars to "in-the-wild"-list 2015-10-28 18:00:16 +01:00
Kevin Decker d20f02d10d Merge pull request #1121 from shinypb/undefined_partial_error_message
Include partial name in 'undefined partial' exception message
2015-10-19 12:01:39 -05:00
Mark Christian 33b53ef989 Use template string for error message and double-quotes for quoting partial name 2015-10-19 09:44:53 -07:00
Mark Christian f08d48764d Include partial name in 'undefined partial' exception message 2015-10-19 09:22:54 -07:00
kpdecker 9365b82900 v4.0.3 2015-09-23 22:41:14 -05:00
kpdecker bf091777a9 Update release notes 2015-09-23 22:40:45 -05:00
kpdecker 94c840b6ed Create data frame for @partial-block
Fixes #1099
2015-09-23 22:08:59 -05:00
kpdecker 861d6f7b8d Fix tests under IE 2015-09-23 21:59:58 -05:00
kpdecker fffb5a985f Fix iteration over undefined values
Allow for iteration on undefined values, but special case undefined and null to prevent rendering errors when not running in strict mode.

Fixes #1093
2015-09-23 21:17:57 -05:00
kpdecker 08781798f5 Allow for escaped ] characters in [] IDs
Allows for ] literal characters to be used within [] IDs by prefixing them with the \ character. `\` literal at the end of the  may be referenced by the `\\` sequence if conflicting. Under most circumstances the `\\` sequence will continue to work.

Potentially breaking change for users of [] ids that have `\\` anywhere in the id or `\` at the end of the id.

Fixes #1092
2015-09-16 16:15:01 -05:00
kpdecker 641fe335df Update travis to test node 0.12 and 4.0.0 2015-09-16 16:15:01 -05:00
Kevin Decker 733b7c8752 Merge pull request #1091 from nikolas/patch-2
fix typo in release notes
2015-09-04 15:18:48 -05:00
Kevin Decker 9a86e40970 Merge pull request #1090 from nikolas/patch-1
grammar fixes in 4.0.0 release notes
2015-09-04 15:18:25 -05:00
Nik Nyby efad390ffc fix typo in release notes 2015-09-04 15:53:31 -04:00
Nik Nyby 3696d23752 grammar fixes in 4.0.0 release notes 2015-09-04 15:47:52 -04:00
kpdecker a5a3ab01d3 v4.0.2 2015-09-04 09:13:16 -05:00
kpdecker c83354101e Update release notes 2015-09-04 09:12:48 -05:00
kpdecker 7b1fdf814c Fix use of decorators within partials
If a decorator is used within a partial but not in the calling template, the hash is not passed in. For now error on the side of always including as just assigning values has minimal overhead.

Fixes #1089
2015-09-04 09:09:17 -05:00
kpdecker c7b28a65da v4.0.1 2015-09-02 21:21:36 -05:00
kpdecker 9a27693fae Update release notes 2015-09-02 21:21:06 -05:00
kpdecker 2d9a5151e8 Add source map flag for istanbul fork 2015-09-02 21:20:03 -05:00
kpdecker 3934b3ab92 Remove duplicate dependency 2015-09-02 21:19:53 -05:00
kpdecker 05b82a203e Fix failure when using decorators in partials 2015-09-02 20:43:54 -05:00
kpdecker f71f4e759e Better assert message in shouldThrow 2015-09-02 20:42:49 -05:00
kpdecker 6e9198b207 Remove duplicated release notes 2015-09-02 20:42:23 -05:00
kpdecker cc8ed7a9e7 Update subset of npm packages 2015-09-02 11:40:59 -05:00
kpdecker 454d20e3b2 Remove keen code
This hasn’t been running and is horribly out of date.
2015-09-02 11:40:35 -05:00
kpdecker 0aef72cb8e Update to latest eslint 2015-09-01 17:56:32 -05:00
kpdecker bff5fab8f9 v4.0.0 2015-09-01 08:19:14 -05:00
kpdecker f97b99e0cd Update release notes 2015-09-01 08:18:41 -05:00
kpdecker 6c07367cd6 Update release notes 2015-09-01 08:15:48 -05:00
kpdecker 83b8e846a3 Escape = in HTML content
There was a potential XSS exploit when using unquoted attributes that this should help reduce.

Fixes #1083
2015-09-01 01:44:35 -05:00
kpdecker b0d217e13d Rev runtime compiler revision 2015-09-01 01:43:00 -05:00
kpdecker c13c7df73d Add basic decorators docs
Fixes #1088
2015-09-01 01:29:25 -05:00
kpdecker b63d74a7b3 Add explicitPartialContext compiler flag
Fixes #1032
2015-09-01 01:05:00 -05:00
kpdecker e7a64f018c Merge branch 'decorators' 2015-09-01 00:48:20 -05:00
Kevin Decker 0f5061e445 Merge pull request #1087 from denniskuczynski/each_object_empty_string_key
Fix #each when last object entry has empty key
2015-08-30 17:27:52 -05:00
Dennis Kuczynski 7c896a074d Fix #each when last object entry has empty key 2015-08-30 10:43:24 -04:00
Kevin Decker e70430dd2e Merge pull request #1084 from blendlabs/master
Bump uglify version to avoid vulnerability flag.
2015-08-25 12:38:11 -05:00
John Steidley 9d4353c35c Bump uglify version to fix vulnerability 2015-08-24 17:06:40 -07:00
Kevin Decker c727e1f97c Merge pull request #1076 from wycats/partial-block
Implement partial blocks
2015-08-23 23:45:17 -05:00
kpdecker 6c45f49b24 Implement decorator helper method 2015-08-22 11:13:08 -07:00
kpdecker 495cd05a7e Implement inline partials
Allows for partials to be defined within the current template to allow for localized code reuse as well as for conditional behavior within nested partials.

Fixes #1018
2015-08-22 11:13:08 -07:00
kpdecker 452afbf2ff Implement block decorators
These allow for a given block to be wrapped in helper methods or metadata and allow for more control over the current container and method before the code is run.
2015-08-22 10:59:34 -07:00
kpdecker 408192ba9f Add decorator parsing 2015-08-22 10:59:34 -07:00
kpdecker 2a4a5447f5 Implement decorator environment and registration 2015-08-22 10:59:34 -07:00
kpdecker 1c274088c1 Update AST doc for partial block 2015-08-22 10:59:08 -07:00
kpdecker 0b5e82e1c3 Add whitespace control to partial block statements 2015-08-22 10:59:08 -07:00
kpdecker 233caf3f7c Create validateClose helper method
Avoid duplicating the logic needed to check for close block mismatches.
2015-08-22 10:59:08 -07:00
kpdecker 91ffd32cad Implement partial blocks
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
2015-08-22 10:59:08 -07:00
kpdecker 2571dd8e8e Improve sanity checks in compiler and visitor 2015-08-22 10:58:44 -07:00
Kevin Decker 729d9fdffe Merge pull request #1081 from hashchange/patch-1
Add Marionette.Handlebars to "in the Wild" list
2015-08-21 18:17:56 -07:00
hashchange a634cd3219 Add Marionette.Handlebars to "in the Wild" list 2015-08-21 13:32:20 +02:00
kpdecker 95d84badca Drop AST constructors in favor of JSON
These were little more than object literal statements that were less clear due to their use of index-based arguments.

Fixes #1077
2015-08-18 23:57:27 -07:00
kpdecker 9a2d1d6009 Pass container rather than exec as context
There is no real need for us to do `.call(container` other than for backwards compatibility with legacy versions. Using the 4.x release as a chance to optimize this behavior.
2015-08-18 23:54:04 -07:00
kpdecker 08093d72f0 Remove unused parameters 2015-08-18 23:14:09 -07:00
kpdecker 958273c2e6 Add object option to test runner 2015-08-18 23:13:15 -07:00
kpdecker 1c2b74e537 Run node tests last
This covers all of the test cases and generally have better stack traces so we want to have these featured more prominently.
2015-08-18 23:06:23 -07:00
kpdecker b85cad8df6 Fix travis config typo 2015-08-14 18:53:08 -05:00
kpdecker d21de5d569 Increase travis git window to avoid test failures
https://twitter.com/travisci/status/288390896339267584
2015-08-14 15:45:41 -05:00
kpdecker 519fac5081 Link to try handlebars from readme 2015-08-14 15:20:45 -05:00
kpdecker 624a4c71aa Cleanup stack traces for test assertions 2015-08-14 15:20:09 -05:00
kpdecker 3ce04ddd23 Include inline source maps in babel build 2015-08-14 15:19:37 -05:00
kpdecker ea3a5a1eb6 Add ignoreStandalone compiler option
Fixes #1072
2015-08-13 01:51:17 -05:00
kpdecker 269dd492bb Link to installation docs 2015-08-12 22:37:26 -05:00
kpdecker 2ae0ef6eef Add license information to bower.json
Fixes #1074
2015-08-12 02:45:51 -05:00
kpdecker 06d515a89d Ignore empty when iterating on sparse arrays
Fixes #1065
2015-08-04 12:40:33 -05:00
kpdecker 0de8dac702 Add support for string and stdin precompilation
Fixes #1071
2015-08-04 12:34:28 -05:00
kpdecker d716fd01c5 Remove no-shadow rule 2015-08-04 12:33:19 -05:00
kpdecker 77e6bfc5a1 Simplify object assignment generation logic 2015-08-04 12:33:05 -05:00
kpdecker 00f74420f9 Convert precompiler template loading to async 2015-08-04 10:55:51 -05:00
kpdecker a62cbad95a Refactor precompiler API into two phase
Load templates and then parse them in a distinct operation. This will allow us to use other input sources such as stdin and strings.
2015-08-04 09:26:19 -05:00
kpdecker ba31ef8ae4 Increase sauce test concurrency 2015-08-03 23:43:13 -05:00
kpdecker a44fe470b4 Include doctype in amd harness 2015-08-03 23:43:00 -05:00
kpdecker bb9831b73e Specify platform for firefox in sauce tests
Works around what appears to be an init issue within Sauce.
2015-08-03 23:42:50 -05:00
kpdecker c532b89af6 Fix parser declaration under amd builds 2015-08-03 22:45:39 -05:00
kpdecker 72eb10ca4b Revert "Pull sauce tests out of CI"
This reverts commit e2ba22eaad.
2015-08-03 22:27:25 -05:00
kpdecker bd643ce12b Pull out duplicated documentation from readme
Fixes #1006
2015-08-03 21:13:47 -05:00
kpdecker 93b07605cd Bulletproof AST.helpers.helperExpression
Avoid undefined values and potential false positives from other type values such as partials.

Fixes #1055
2015-08-03 20:31:57 -05:00
kpdecker 85750f8d0a Use += in printer 2015-08-03 18:51:46 -05:00
kpdecker fac71ce700 Fix incorrect variable removal 2015-08-03 17:59:23 -05:00
kpdecker 324d615726 Enforce 100% code coverage 2015-08-03 17:49:47 -05:00
kpdecker c3cbaa25a4 Fix partial handling with different context 2015-08-03 17:27:38 -05:00
kpdecker 9b1f9c7e44 Style updates 2015-08-03 17:03:37 -05:00
kpdecker 8e868ab225 Always return string responses
Certain optimizations for simple templates could result in objects returned by helpers returned rather than their string representation, resulting in some odd edge cases. This ensures that strings are always returned from the API for consistency.

Fixes #1054.
2015-08-03 16:08:23 -05:00
kpdecker 5d4b8da344 Pass undefined fields to helpers in strict mode
This allows for `{{helper foo}}` to still operate under strict mode when `foo` is not defined on the context. This allows helpers to perform whatever existence checks they please so patterns like `{{#if foo}}{{foo}}{{/if}}` can be used to protect against missing values.

Fixes #1063
2015-08-03 15:59:53 -05:00
kpdecker 279e038ba7 Avoid depth creation when context remains the same
Creating a new depth value seems to confuse users as they don’t expect things like `if` to require multiple `..` to break out of. With the change, we avoid pushing a context to the depth list if it’s already on the top of the stack, effectively removing cases where `.` and `..` are the same object and multiple `..` references are required.

This is a breaking change and all templates that utilize `..` will have to check their usage and confirm that this does not break desired behavior. Helper authors now need to take care to return the same context value whenever it is conceptually the same and to avoid behaviors that may execute children under the current context in some situations and under different contexts under other situations.

Fixes #1028
2015-08-03 15:15:09 -05:00
kpdecker 1c08771215 Fix track id handling in partials
Fixes #914
2015-08-03 12:16:02 -05:00
kpdecker 9f265b9761 Handle this references properly in track id mode 2015-08-03 12:13:24 -05:00
kpdecker 0aa54f49de Avoid log output in test 2015-08-03 12:13:00 -05:00
kpdecker 1f11cc0186 Remove out of date TODO 2015-08-03 12:12:30 -05:00
kpdecker 9a49d35023 Improve logging API
Adds multiple variable support and the ability to set statement level logging semantics.

This breaks that logger API, cleaning up the manner in which enums are set, but the other behaviors are backwards compatible.

Fixes #956
2015-08-03 11:37:49 -05:00
kpdecker b664997dc3 Move logger into separate module 2015-08-01 22:03:11 -05:00
kpdecker ac82842cb6 Add rest params to es6 supported list 2015-08-01 22:01:41 -05:00
kpdecker efddc3c09c Increase code coverage 2015-08-01 22:01:16 -05:00
kpdecker f3e8b18925 Add istanbul ignore to babel boilerplate 2015-08-01 21:46:45 -05:00
kpdecker 15b55a307b Move helpers into separate modules 2015-08-01 17:54:47 -05:00
kpdecker 231a8d7256 Fix with operator in no @data mode 2015-08-01 16:29:45 -05:00
kpdecker e2ba22eaad Pull sauce tests out of CI
Disabling these until #1069 can resolve whatever the root issue is.
2015-08-01 16:08:57 -05:00
kpdecker 1bb640be41 Allow empty key name in each iteration
Fixes #1021
2015-08-01 16:04:40 -05:00
kpdecker 2a851067b9 Add with block parameter support
Fixes #1042
2015-08-01 15:48:28 -05:00
kpdecker 410141c31e Fix escaping of non-javascript identifiers
The ‘ character would cause invalid javascript to be generated as it was not properly escaped. Switching to JSON.stringify safely handles all potential unescaped cases.
2015-08-01 15:48:28 -05:00
Kevin Decker 060c087e62 Merge pull request #1068 from 0xack13/master
Fix typo
2015-07-30 09:53:25 -05:00
Saleh Batati 5acf77a99a Merge pull request #1 from 0xack13/0xack13-patch-1-fix-typo
Fix typo
2015-07-30 06:08:11 +03:00
Saleh Batati fe057168ae Fix typo 2015-07-30 06:02:51 +03:00
Kevin Decker c83c363a58 Merge pull request #1060 from ericbn/issue-1056
#1056 Fixed grammar for nested raw blocks
2015-07-20 10:49:14 +03:00
Eric Nielsen aa7a45b443 Added comment about Jison's topState() 2015-07-19 12:34:44 -03:00
kpdecker a458fe26f4 Update jsfiddle link to 3.0.3 2015-07-19 11:28:43 +03:00
Eric Nielsen 2f9495c1df Added spec message 2015-07-16 15:15:06 -03:00
Eric Nielsen b9fe7ce618 #1056 Added spec for nested raw block 2015-07-15 14:45:53 -03:00
Eric Nielsen 868ef4b309 #1056 Fixed grammar for nested raw blocks 2015-07-15 14:45:43 -03:00
Kevin Decker 8c09b3c896 Merge pull request #1052 from maqnouch/patch-1
Updated year in License
2015-07-02 23:40:03 +02:00
AQNOUCH Mohammed 9bbc177fd5 Updated year in License 2015-07-02 15:53:10 +00:00
kpdecker 93faffa549 Fix location information for programs
There appears to be a bug in our use of jison causing the parent location information to be reported to programs. I wasn’t able to work through what might be causing this so instead using the location information of the statements collection to generate the proper location information.

This is a bit of a hack but we are very far behind on the Jison release train and upgrading will likely be a less than pleasant task that doesn’t provide us much benefit.

Fixes #1024
2015-06-26 16:58:34 -05:00
Kevin Decker d2fb3a4906 Merge pull request #1037 from tomxtobin/minor-readme-typo-fixes
Fix minor typos in README
2015-06-09 06:07:35 -04:00
Tom X. Tobin d0805e9cfa Fix minor typos in README 2015-06-08 18:37:41 -04:00
Kevin Decker 2c1d509c6c Merge pull request #1019 from aymerick/aymerick-patch-typo-test
Fixes typo in tests
2015-05-06 11:13:43 -05:00
kpdecker 569f288551 Add tests for string contexts
Fixes #1013
2015-05-06 10:48:23 -05:00
Aymerick d0d2168ec2 Fixes typo in tests
mutache => mustache
2015-05-06 14:03:14 +02:00
Kevin Decker 8cba84df11 Merge pull request #1015 from aymerick/fix-test
Fix tokenizer test for double quoted strings
2015-05-05 10:23:16 -05:00
kpdecker 00bfdd782f Fix VERSION update script
This broke after updating to es6 formatted code.

Fixes #1016
2015-05-05 10:20:56 -05:00
Aymerick afd595bd6e Fix tokenizer test for double quoted strings
There is two consecutive tests with the same input data: "{{ foo bar \'baz\' }}"

I suppose the first test should be about testing double quoted string.
2015-05-01 11:35:25 +02:00
kpdecker 891f48b7e9 v3.0.3 2015-04-28 14:52:04 -05:00
kpdecker 0bf79ea5c0 Update release notes 2015-04-28 14:51:32 -05:00
kpdecker 972f521718 Move noConflict implementation to module
DRYs the code to avoid escapes like #1004

Fixes #1004
2015-04-27 10:19:50 -05:00
kpdecker 4a40fc8e2f NPM ignore log files 2015-04-27 10:19:50 -05:00
kpdecker fc13400b6f Remove jshint completely 2015-04-27 10:19:49 -05:00
Kevin Decker 6ecf509385 Merge pull request #1008 from shannonmoeller/patch-1
Added handlebars-layouts to the in-the-wild list
2015-04-25 09:54:25 -05:00
Shannon Moeller d945089565 Added handlebars-layouts to the in-the-wild list
I've written a set of helpers which implement layout blocks similar to Jade, Jinja, Swig, Twig, and others. It seems to be gaining some popularity (around 10k npm downloads per month at present) and I would be honored to have it included in the in-the-wild list in case it may prove useful to others.
2015-04-24 22:29:40 -04:00
kpdecker 2f0b86665f v3.0.2 2015-04-20 03:11:12 -05:00
kpdecker d48e840513 Update release notes 2015-04-20 03:10:49 -05:00
kpdecker 4bed826d0e Update for let and optional parameters 2015-04-20 02:38:28 -05:00
kpdecker 0263aa48bc Run the precompiler module through es6 toolchain 2015-04-20 02:25:53 -05:00
kpdecker ecf60ab1bf Enable additional es6 features in eslint 2015-04-20 02:24:49 -05:00
Kevin Decker c6ab044fa8 Merge pull request #998 from wycats/babel
Add full support for es6
2015-04-16 17:12:40 -05:00
kpdecker fdc94207a3 Fix UMD build target generation 2015-04-16 16:54:30 -05:00
kpdecker e3d3eda2e1 Add full support for es6
Converts the tool chain to use babel, eslint, and webpack vs. the previous proprietary solutions.

Additionally begins enforcing additional linting concerns as well as updates the code to reflect these rules.

Fixes #855
Fixes #993
2015-04-16 16:43:01 -05:00
kpdecker 2a02261a5b Fix IE test freakout 2015-04-14 22:57:15 -05:00
kpdecker a009a97a2e Use captureStackTrace for error handler
Provides more stable error stack reporting when running under v8 environments.
2015-04-14 01:14:18 -05:00
kpdecker 54e743a09a Allow this references in literal statements
Fixes #967
2015-04-14 01:12:39 -05:00
kpdecker a7160a8d27 Expose Visitor in full browser build
Fixes #994
2015-04-13 21:25:53 -05:00
kpdecker dfd141cd35 Allow undefined and null in helper names 2015-04-13 21:18:53 -05:00
kpdecker 37a664bf64 Ignore branches tested without coverage monitoring 2015-04-07 23:38:05 -05:00
kpdecker c699d0b82f Remove vestigial code 2015-04-07 23:37:46 -05:00
kpdecker 2d149e7797 Add undefined and null literal support
This adds the UndefinedLiteral and NullLiteral to AST.

Fixes #990
2015-04-07 23:37:38 -05:00
kpdecker 81a4d50955 Avoid source-map import under AMD
Fixes #989
2015-04-07 22:35:14 -05:00
kpdecker e15af4c849 Add require.js error reporting in tests
Adds test coverage for #989
2015-04-07 21:51:50 -05:00
kpdecker afe730e059 v3.0.1 2015-03-24 14:22:38 -05:00
kpdecker 40894e619b Update release notes 2015-03-24 14:22:22 -05:00
Kevin Decker 1d401a2abc Merge pull request #984 from johneke/master
Adding documentation for passing arguments into partials
2015-03-24 14:20:22 -05:00
johneke a10d907a65 Update README.markdown 2015-03-18 22:55:26 -04:00
johneke fed86c01ad Adding documentation for parameters in partials 2015-03-18 20:23:15 -04:00
kpdecker ab96073c6b Optimize hot path in escapeExpression
Avoid deoptimizations in v8 due to the duct type check on string instances.

Partial fix for #973
2015-03-16 22:06:01 -05:00
kpdecker 64ab232d72 Fix --version flag
Fixes #966
2015-02-28 19:58:23 -06:00
Kevin Decker 8c357b9243 Merge pull request #964 from grassick/patch-1
default is a reserved word
2015-02-28 16:26:39 -06:00
grassick 71690ae39d default is a reserved word
Quoting it to fix in older browsers, especially Android 2.3.x.
2015-02-18 10:57:02 -05:00
Kevin Decker 3c9ff77e68 Merge pull request #962 from pismute/master
Add dashbars' link on README.
2015-02-16 09:01:07 -06:00
Changwoo Park 5ede36b8fd Add dashbars' link on README. 2015-02-16 19:29:13 +09:00
kpdecker c15f90e513 Fix typo 2015-02-10 20:40:53 -06:00
kpdecker 0099e16a01 v3.0.0 2015-02-10 00:14:55 -06:00
kpdecker a8848d0af9 Update release notes 2015-02-10 00:14:42 -06:00
kpdecker ca6152d2f7 Merge branch 'marcioj-boolean-literals' 2015-02-09 23:55:25 -06:00
kpdecker 55dc82d938 Update AST docs for literal helper names 2015-02-09 23:55:01 -06:00
kpdecker 39121cf8f5 Handle all potential literal values
Adds support for literal helper names in a few missing cases such as block expressions and subexpressions.
2015-02-09 23:54:46 -06:00
Marcio Junior 07f27843dc Transform literals to path expressions in mustache nodes 2015-02-08 19:31:38 -02:00
Marcio Junior 5cc326d425 Test compilation of literal values in mustaches 2015-02-08 19:31:38 -02:00
Marcio Junior f857471cc8 Boolean literals can be passed to mustaches 2015-02-08 19:31:38 -02:00
kpdecker fd8484798c Fix console.log cleanup 2015-02-07 14:31:54 -06:00
kpdecker 810d86d474 Fix global leak in test 2015-02-07 13:28:27 -06:00
kpdecker cfbef2585d Fail over to console.log if lacking console method
This improves logger resiliency under older browsers.
2015-02-07 13:23:10 -06:00
kpdecker 95421271e3 Change default log level to info
Eventually we want to fix this API so that you can provide per-instance levels, etc but for now this is a quick fix.

Fixes wycats/handlebars-site#91
2015-02-07 12:31:19 -06:00
Kevin Decker 752a28f24c Merge pull request #943 from ogennadi/patch-2
Properly formatted dot
2015-01-21 06:16:21 -06:00
Kevin Decker 05bda7ed0c Merge pull request #944 from ogennadi/patch-3
Clarified a long sentence
2015-01-21 06:15:58 -06:00
Oge Nnadi 1f6a94424d Clarified a long sentence 2015-01-20 11:01:11 -08:00
Oge Nnadi 8626fa8bbe Properly formatted dot 2015-01-20 10:59:38 -08:00
Kevin Decker 9f8daf9076 Merge pull request #941 from wycats/dynamic-partial
Add support for dynamic partial names
2015-01-18 20:13:25 -06:00
kpdecker 884bf15536 Avoid direct references to sexpr in statements
This allows us to avoid creating unnecessary AST nodes and avoids things like isHelper.

Side effect of these changes is that @data functions can now have data parameters passed to them.
2015-01-18 17:27:27 -06:00
kpdecker 999da739a6 Update for proposed SubExpression dependency break
Avoids parsing against SubExpressions and instead inlines the content that a subexpression otherwise would have. This can still be based via duck typing so should not add much overhead to the compiler.
2015-01-18 14:21:04 -06:00
kpdecker d567d9c345 Update PartialExpression AST docs 2015-01-18 13:29:15 -06:00
kpdecker cb51b82b8e Add support for dynamic partial names
Uses the subexpression syntax to allow for dynamic partial lookups. Ex:

```
{{> (helper) }}
```

Fixes #933
2015-01-18 13:23:45 -06:00
kpdecker b0b522b4f8 Add tests for partial comment whitespace control 2015-01-17 19:19:30 -06:00
kpdecker 3cf47abe19 Include error info when throwing type error 2015-01-17 19:19:30 -06:00
Kevin Decker b7488acf89 Merge pull request #940 from mattflaschen/addMissingReservedWords
Add missing reserved words so compiler knows to use array syntax:
2015-01-17 18:32:58 -06:00
Matthew Flaschen c9d723e0fb Add missing reserved words so compiler knows to use array syntax:
* await
* null
* true
* false

IE 8 was failing to compile Handlebars-generated source code
because it had helpers.null.

I came up with this list by diffing
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
against the ones Handlebars already had.

I added two corresponding tests for nameLookup.
2015-01-17 19:04:38 -05:00
kpdecker c5fe2527b3 Ignore coverage dir in npm package
Fixes #937
2015-01-17 14:51:49 -06:00
Kevin Decker 9c40c07941 Merge pull request #938 from diwo/patch-1
Fix example using #with helper
2015-01-15 10:57:23 -08:00
Dickson Wong 9c7641f63f Fix example using #with helper
Example using #with helper should close tag using {{/with}}
2015-01-14 15:46:54 -08:00
Kevin Decker 96a5cf5ba1 Merge pull request #932 from wycats/strict
Implement strict and assumeObject modes
2014-12-31 19:28:05 -06:00
Kevin Decker 4b2146b12d Merge pull request #930 from wycats/visitor-update
Add parent tracking and mutation to AST visitors
2014-12-29 16:59:21 -06:00
kpdecker 6650957f79 Implement strict and assumeObject modes
Causes templates to throw when lookup fields are not defined within the context. Strict mode will throw when any field is omitted. assumeObjects mode maintains the existing behavior of outputting an empty response when fields are not defined but allows for performance/size optimizations by not doing safety checks for intermediate objects in the lookup chain.

Strict mode effectively disables the helperMissing and inverse handling for blockHelperMissing as templates will throw rather than call those features for missing data fields.

Fixes #651
Fixes #805
2014-12-27 14:59:36 -06:00
kpdecker ec798a7c44 Merge branch 'master' into visitor-update 2014-12-27 13:30:20 -06:00
kpdecker b764fb1ded Fix sourcemap test 2014-12-27 13:17:55 -06:00
kpdecker 0a9fc171b0 Fix block param evaluation under older IE 2014-12-27 13:13:52 -06:00
kpdecker 9c9a972a0c Safely handle source map in browser tests 2014-12-27 13:01:13 -06:00
kpdecker b474630c7d Strip unnecessary whitespace in template output 2014-12-27 12:58:28 -06:00
kpdecker f147050513 Update dependencies 2014-12-27 12:12:21 -06:00
kpdecker daa8f3dd05 Fix duplicate return statement on simple templates 2014-12-27 10:05:05 -06:00
kpdecker be0ba75925 Fix case-insensitive file system compile issue 2014-12-26 11:34:48 -06:00
kpdecker 9ff3daf785 Add parent tracking and mutation to AST visitors
Fixes #916
2014-12-26 11:32:30 -06:00
kpdecker 8babe05953 Bump code coverage 2014-12-26 11:12:47 -06:00
kpdecker 396795c983 Implement block parameters
Fixes #907
2014-12-26 00:31:57 -06:00
kpdecker 9e907e6785 Expose AST helpers in public API 2014-12-21 22:32:50 -06:00
kpdecker 6d2239d8ac Update fix for #926 for 3.0 codeline 2014-12-17 00:40:28 -06:00
kpdecker 59e80c5103 Track depths on deduped children
Fixes a very specific error case where deduped children won’t receive the depths object due to it being omitted by the caller when optimizing.

Fixes #926
2014-12-16 17:59:32 -06:00
kpdecker 69b1bdd32e Avoid unnecessary this. reference 2014-12-16 12:57:46 -06:00
kpdecker c05fad8b18 Fix remaining depth arg 2014-12-16 12:57:46 -06:00
kpdecker 203df9d5b7 Remove unused vars and add jshint checking 2014-12-16 12:57:46 -06:00
kpdecker 0d396ccd30 Remove Compiler.depths parameter
This is no longer necessary with the depths array tracking and adds compiler overhead.
2014-12-16 12:57:46 -06:00
Kevin Decker ae8c1d229f Merge pull request #918 from oneeman/submodule-instr-in-contributing
Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos
2014-12-08 11:39:22 -08:00
Or Neeman e7f51c2248 Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos elsewere 2014-12-07 09:00:56 -06:00
kpdecker 3238645f26 Allow blockParams on chained inverse statements 2014-11-29 18:38:22 -06:00
kpdecker f84f76f006 Add basic docs for JavaScriptCompiler override API 2014-11-29 18:20:11 -06:00
kpdecker 96c1300f15 Drop unused JavaScriptCompiler.namespace param 2014-11-29 18:11:58 -06:00
kpdecker b6ba2dcdb0 Move compileProgram out of visitor methods 2014-11-29 18:02:58 -06:00
kpdecker 5b0efc7e91 Track source node via accept call rather vs opcode 2014-11-29 18:02:58 -06:00
Kevin Decker d4070c3667 Merge pull request #915 from wycats/ast-update
Ast update
2014-11-29 18:02:12 -06:00
kpdecker a655aedb5c s/Subexpression/SubExpression/ 2014-11-29 17:59:09 -06:00
kpdecker 928ba56b95 Rework strip flags to make clearer at in AST level
Rather than keeping state in the AST, which requires some gymnastics, we create a separate visitor flow which does the top down iteration necessary to calculate all of the state needed for proper whitespace control evaluation.
2014-11-28 23:13:06 -06:00
kpdecker 8a6796e5c0 Move Jison parsing out of AST into helpers 2014-11-28 17:26:52 -06:00
kpdecker 95b23095c0 First crack at compiler API docs 2014-11-28 16:55:53 -06:00
kpdecker ffc9fb5007 Add original to BooleanLiteral 2014-11-28 16:55:41 -06:00
kpdecker 6a7a8c803d Rename AST objects to match type names 2014-11-28 14:42:24 -06:00
kpdecker f990cf0064 Treat partial exec in a manner closer to helpers
This helps unify the code handling and will also be needed to support string/id tracking on partials.
2014-11-27 09:11:03 -06:00
kpdecker 1124908d2a Update subexpression and hash AST constructs 2014-11-27 08:37:48 -06:00
kpdecker e1cba432a6 Simplify Path and Sexpr calculated flags 2014-11-27 07:32:15 -06:00
kpdecker 5c921cafeb Replace DataNode and IdNode with PathNode
This is a breaking change for string mode users as there is no longer a distinct type for data parameters. Instead data consumers should look for the @ prefix value.
2014-11-26 20:36:36 -06:00
kpdecker 697bbe59ca Update literal ast nodes for new spec 2014-11-26 19:22:09 -06:00
kpdecker 2a4819d75c Update statement node ASTs 2014-11-26 19:04:18 -06:00
kpdecker 5cfe6a0be1 Update MustacheNode for new AST structure 2014-11-26 09:31:46 -06:00
kpdecker df421f1e4b Update ProgramNode to better match SpiderMonkey 2014-11-26 09:18:33 -06:00
kpdecker 61dd721ca2 Update AST location info to match SpiderMonkey
Part of #889
2014-11-26 09:01:03 -06:00
kpdecker 3a9440f954 Make each helper data uniform
Provide @key and @last value for all forms of iteration.

Fixes #910
2014-11-26 08:02:55 -06:00
kpdecker e4c874d1af Update to latest es6-module-packager 2014-11-26 08:00:23 -06:00
kpdecker 5594416121 Export the default object for handlebars/runtime
Maintains consistency with the prior export pattern, while avoids the knarly default loading issues.
2014-11-26 07:49:29 -06:00
kpdecker c4b3c901a5 Provide Handlebars.noConflict implementation
Allows for users who are loading via a global pattern to avoid conflicting with other instances of the library.

Fixes #887
2014-11-26 07:48:45 -06:00
kpdecker 617dd57162 Lookup partials when undefined 2014-11-25 23:15:11 -06:00
kpdecker d044a8c99d Optimize variable declaration 2014-11-25 23:15:11 -06:00
Kevin Decker bf993528d1 Merge pull request #906 from mmun/block-params
Add parser support for block params
2014-11-12 21:36:25 -06:00
Martin Muñoz b8a9f7264d Add parser support for block params 2014-11-11 21:35:10 -05:00
Kevin Decker b3b5b35889 Merge pull request #856 from MajorBreakfast/patch-1
jspm compatibility
2014-11-11 15:05:10 -06:00
MajorBreakfast 80b5b24cce jspm compatibility
This will enable compatibilty with jspm as soon as https://github.com/wycats/handlebars.js/issues/855 has been addressed.
2014-11-10 15:12:58 +01:00
kpdecker 4a2afa9d02 Propagate options for client compiled partials
Fixes #901
2014-11-08 19:06:40 -06:00
kpdecker 4415f3151d Merge branch 'mmun-remove-block-mustache' 2014-11-08 18:57:19 -06:00
kpdecker bf8cc50798 Merge branch 'remove-block-mustache' of github.com:mmun/handlebars.js into mmun-remove-block-mustache
Conflicts:
	lib/handlebars/compiler/helpers.js
	spec/parser.js
2014-11-08 18:57:06 -06:00
kpdecker ac208b1bcd Only provide aliases for multiple use calls
Fixes #903
2014-11-08 18:53:43 -06:00
kpdecker 8dfeabdd16 Fix missing coverage 2014-11-08 18:11:29 -06:00
kpdecker 6d7696ef66 Remove unnecessary test stub 2014-11-08 18:09:15 -06:00
Kevin Decker 8e6e9fbc60 Merge pull request #902 from wycats/source-map
Generate Source Maps
2014-11-08 18:04:56 -06:00
kpdecker 1429587dd4 Provide default mapping for boilerplate code 2014-11-08 17:50:01 -06:00
kpdecker 0dbeeb4503 Add source map output support to the CLI 2014-11-08 17:50:01 -06:00
kpdecker 3ee0682247 Generate source maps
Allow the precompiler to generate source maps when the srcFile parameter is passed.

This refactors large chunks of the code generation pipeline, allowing metadata to be associated with code chunks as well as breaking out much of the code generation logic into a separate helper.
2014-11-08 17:50:01 -06:00
kpdecker 249f559104 Include location information in all opcodes 2014-11-08 17:50:01 -06:00
Kevin Decker ea6b0be910 Merge pull request #892 from wycats/else-if
Implement parser for else chaining of helpers
2014-11-08 17:49:14 -06:00
Kevin Decker e779ecf12d Merge pull request #890 from wycats/to-html-safe
Use toHTML vs. instanceof checks for SafeString
2014-11-08 17:47:25 -06:00
kpdecker 01a22e61df Use toHTML vs. instanceof checks for SafeString
Allows for us to play nicely in environments such as Node that could have multiple versions of the library loaded. Also allows for implementors to provide their own behavior, provided they know what they are doing.

Fixes #886
2014-11-08 17:46:53 -06:00
Kevin Decker c8af90f697 Merge pull request #863 from tomByrer/patch-1
+ jsDelivr CDN info
2014-11-08 17:44:22 -06:00
Kevin Decker b5f03c95ab Merge pull request #870 from max-b/master
Registering undefined partial throws exception.
2014-11-08 17:43:41 -06:00
kpdecker d595ebc0e2 Simplify program generation logic 2014-11-08 14:36:17 -06:00
kpdecker d47e4dd1f7 Bump test coverage 2014-11-08 14:35:23 -06:00
kpdecker 83bcbee222 Ignore unused code paths
We don’t want to remove these as the generic code may need it in the future, but these code paths are not triggered through the existing language constructs.
2014-11-08 14:35:23 -06:00
kpdecker 30034c75e7 Merge setupParams and setupOptions 2014-11-06 09:43:43 -06:00
kpdecker d3bd1a523d Simplify flushInline implementation 2014-11-05 23:23:43 -06:00
kpdecker 9665379cdc Use terinary operator for inline appends
Allows for append operations to avoid breaking inline chain.
2014-11-05 22:28:12 -06:00
kpdecker a7000f8129 Provide stubbed visitor implementation
Part of #889
2014-11-05 21:57:09 -06:00
Martin Muñoz a5d0491d57 Remove RawBlockNode in favor of prepareRawBlock helper 2014-11-05 11:00:39 -05:00
Martin Muñoz 12e3d210e8 Simplify BlockNode by removing intermediate MustacheNode 2014-11-02 18:19:20 -05:00
kpdecker 632fadcea3 Add preventIndent option for partials
This disables the standalone partial indent behavior required by the Mustache spec and allows for users to utilize partials in the same manner as under 1.x.

Fixes #858
2014-11-02 12:19:56 -06:00
kpdecker 7c220b9af5 Add test for whitespace control w/ partial indent
Verifies the behaviors discussed in #858
2014-11-02 12:09:37 -06:00
kpdecker f30b3ea329 Allow whitespace control on comments
This changes the call signature for the CommentNode constructor, which is a potentially breaking change for AST users.

Fixes #866
2014-11-02 11:55:52 -06:00
kpdecker 9fbf7aa753 Fix help message for CLI
Fixes #895
2014-11-02 11:08:42 -06:00
Kevin Decker 3e1840a75d Merge pull request #874 from ogwiz2/patch-1
Capitalization change in comments for consistency
2014-10-27 23:12:25 -05:00
kpdecker 4282668d47 Implement parser for else chaining of helpers
Allows users to chain multiple helpers together using their inverse callbacks. I.e.

```
{{#if foo}}
{{else if bar}}
{{else}}
{{/if}}
```

The control flow here effectively causes the helpers to be nested. The above is actually syntactic sugar for this:

```
{{#if foo}}
{{else}}
  {{#if bar}}
  {{else}}
  {{/if}}
{{/if}}
```

Any helper may be used in this manner, the only requirement is they support normal calls and inverse calls.

Introduces a breaking change in that `{{else foo}}` may no longer be used as a root level operator. Instead `{{^foo}}` must be used.

Fixes #72.
2014-10-27 19:17:16 -05:00
Alex Jeng 4aab967796 Capitalization change in comments for consistency 2014-09-23 11:59:30 -07:00
Maxb b87e601063 Fixing indentation in spec/partials.js 2014-09-19 11:46:02 -07:00
Maxb a0e2e200d9 Registering undefined partial throws exception. 2014-09-19 11:35:16 -07:00
Kevin Decker 3133af2a21 Merge pull request #869 from jonsadka/patch-1
Update FAQ.md
2014-09-19 01:02:40 -05:00
Jon Sadka 5fe7ef63d8 Update FAQ.md
Fixed misspelling of sufficent to sufficient on Line 60
2014-09-18 22:08:51 -07:00
tomByrer 62ed555b2c + jsDelivr CDN info
Thanks to libgrabber, every time a [new release is posted](https://github.com/components/handlebars.js/releases), jsDelivr will find it & automatically upload it.  If you change your file structure, please updated the [include section](https://github.com/jsdelivr/jsdelivr/blob/master/files/handlebarsjs/update.json) or ask us to do it.

I was going to link CDNJS, but they [haven't uploaded to v2.0.0](http://cdnjs.com/libraries/handlebars.js).  Nothing against those cats; we [use their network](http://www.jsdelivr.com/network.php).
2014-09-06 08:08:23 -06:00
kpdecker 8aa2a34a8d Update jsfiddle link 2014-09-02 00:14:10 -05:00
kpdecker aaa590d846 Fix non-prerelease handling for gemspec 2014-09-01 21:42:45 -05:00
kpdecker 1eb2b04aa1 v2.0.0 2014-09-01 21:28:59 -05:00
kpdecker 2e75199664 Update release notes 2014-09-01 21:28:51 -05:00
kpdecker 0670f656fb Update jsfiddle to 2.0.0-beta.1 2014-08-27 09:27:57 -05:00
kpdecker 4d17e3c209 Add contrib note regarding handlebarsjs.com docs 2014-08-27 01:49:52 -05:00
kpdecker 58fb258016 Cleanup var names slightly 2014-08-27 01:49:23 -05:00
kpdecker ed3ae9af59 Drop cdnjs release step 2014-08-26 19:38:40 -05:00
kpdecker 64d5481c00 Play nice with gemspec version numbers 2014-08-26 19:38:32 -05:00
kpdecker cee52ea68f v2.0.0-beta.1 2014-08-26 18:32:15 -05:00
kpdecker d33b362d97 Update release notes 2014-08-26 18:27:11 -05:00
Kevin Decker c5acea75aa Update FAQ.md 2014-08-25 23:37:40 -05:00
kpdecker 4f01f650dc Render false literal as “false”
Fixes #827
2014-08-25 23:35:43 -05:00
kpdecker 3c869866c8 Update FAQ with comment on UMD vs. AMD build
Fixes #796
2014-08-25 22:55:05 -05:00
kpdecker ca1486b960 Prune unused code 2014-08-25 22:39:17 -05:00
kpdecker dfca676d63 Fix test path 2014-08-25 22:39:01 -05:00
kpdecker 09d3c74c7f Convert build to expose UMD from the default files 2014-08-25 22:06:15 -05:00
kpdecker c98147b31f Move uglify include past conditional 2014-08-25 22:05:38 -05:00
kpdecker b41459055a Expose default compatibility wrapper for umd build 2014-08-25 22:04:11 -05:00
kpdecker 84342b6215 Add template isTop flag 2014-08-25 21:15:26 -05:00
kpdecker 24d63b6966 Update Ember testing instructions 2014-08-25 21:14:56 -05:00
kpdecker 36f00db073 Fix compiler version tag 2014-08-25 21:13:31 -05:00
kpdecker 64f422b57f Rev runtime version 2014-08-25 13:49:20 -05:00
kpdecker dc9a2ed7eb Refactor content blocks to ignore lines
We can simplify our previous standalone determination logic by merging content blocks again.

Fixes #854
2014-08-25 13:27:28 -05:00
kpdecker 980c38cebf Expose escapeExpression on the root object 2014-08-25 00:34:19 -05:00
kpdecker 31f6fd7131 Merge mustache compatibility sections 2014-08-25 00:34:05 -05:00
kpdecker c9886fdc0f FAQ tweaks 2014-08-25 00:33:46 -05:00
kpdecker 501a640330 Allow passing depths to _child 2014-08-25 00:32:57 -05:00
kpdecker 477a26913a Optimize compiler opcode call 2014-08-24 18:16:11 -05:00
kpdecker 4ce474d3e4 Update issue filing guidelines 2014-08-24 15:56:27 -05:00
kpdecker 3e6bf8b0f6 Link to current performance tests in readme 2014-08-24 15:45:10 -05:00
kpdecker e5aeca648a Create CONTRIBUTING.md
Cleans up some of the process focused content in the README and also adds nice message to github issue tracker, etc.
2014-08-24 15:28:22 -05:00
kpdecker 2b3fdf7ea8 Additional test coverage cleanup
Also fixes the template._child implementation which broke with the depthed work.
2014-08-23 18:52:30 -05:00
kpdecker 0c7c37df6a Restore helperMissing for ambiguous statements
Fixes #318 (regression)
Partial fix for #783
2014-08-23 18:26:57 -05:00
kpdecker cde008b49f Cleanup from code coverage report 2014-08-23 17:44:45 -05:00
kpdecker 84d646bb5d Move strip processing into AST helper logic
We already have to track these behaviors for the standalone parsing and rather than having two whitespace pruning implementations this moves all of the behavior into one place.

Fixes #852
2014-08-23 17:44:20 -05:00
kpdecker ccd803ff15 Replace string value rather than add omit flag 2014-08-23 16:37:30 -05:00
kpdecker 0528b91644 Add original value tracking to ContentNode 2014-08-23 16:33:14 -05:00
kpdecker 35b196d069 Merge branch 'mmun-refactor-parser' 2014-08-23 16:29:50 -05:00
kpdecker 529e2b6796 Merge branch 'refactor-parser' of github.com:mmun/handlebars.js into mmun-refactor-parser
Conflicts:
	lib/handlebars/compiler/ast.js
	spec/ast.js
	src/handlebars.yy
2014-08-23 16:29:22 -05:00
kpdecker 3531e04117 Optimize replaceStack for inline methods
Only use case was with inline input so most of this code was unnecessary.
2014-08-23 07:43:09 -05:00
kpdecker c3fde1c01c Remove unnecessary stack variable from blockHelper 2014-08-23 07:42:24 -05:00
kpdecker 67b3f40eb2 Merge branch 'evensoul-master' 2014-08-23 07:36:16 -05:00
kpdecker 19ce981296 Do not flush subexpressions
They are no longer duplicated with the new helper calling pattern and this also introduced stack corruption issues due to improper value lookups.

Fixes #767
Fixes #768
2014-08-23 07:36:01 -05:00
Marko Zabcic f4b8c5260c added test multiple subexpressions in a hash with context 2014-08-23 06:56:22 -05:00
Kevin Decker cf343a1949 Merge pull request #849 from wycats/helper-undefined
Fix undefined handling for pathed lookups
2014-08-15 02:07:24 -05:00
kpdecker 3ce105ae89 Fix undefined handling for pathed lookups
Fixes #837
2014-08-15 01:45:50 -05:00
kpdecker ae44e071d8 Test case for #837 2014-08-15 01:45:50 -05:00
kpdecker 2f14fa57e5 Fix/simplify tests under sauce 2014-08-15 01:02:26 -05:00
kpdecker f2b5519e76 Strip dead code from javascript compiler 2014-08-15 00:40:07 -05:00
kpdecker cc5de5df3c Fix compat partial exec without data 2014-08-15 00:38:23 -05:00
kpdecker d05245b5f5 Make depthed lookup call non-literal
Prevents duplicate calls from being made.
2014-08-15 00:37:57 -05:00
kpdecker 084e8fe1b7 Provide better error on unexpected template
Fixes #806
2014-08-15 00:06:40 -05:00
kpdecker 9f07a34955 Additional test coverage 2014-08-15 00:05:37 -05:00
kpdecker 6f22ec1e01 Remove nested function eval in blockHelperMissing
This only worked when a function returned a function, which was then evaluated. This seems like unexpected behavior so this was dropped.
2014-08-14 23:10:17 -05:00
kpdecker c71ca34fd3 Add FAQ entry for .default export
Fixes #824
2014-08-14 22:00:59 -05:00
kpdecker 85b67d7f3d Use public url for spec submodule 2014-08-14 12:31:01 -05:00
kpdecker 9e3f824bf5 Fix compiler program de-duping 2014-08-14 12:27:39 -05:00
kpdecker b94656a31f Drop unused Compiler.disassemble method 2014-08-14 12:27:23 -05:00
kpdecker 3cea85b2b0 Drop unused DECLARE opcode 2014-08-14 12:27:02 -05:00
kpdecker 4d0e3c98eb Drop unused registerHelper inverse parameter 2014-08-14 12:25:58 -05:00
kpdecker cb22ee5681 Increase test coverage a touch 2014-08-14 12:25:35 -05:00
kpdecker 9ab31e1fc6 Add istanbul code coverage 2014-08-14 01:11:55 -05:00
kpdecker 1bd8d38c3c Update compatibility documentation
Provides an official statement regarding the issues discussed in #425
2014-08-14 00:48:19 -05:00
kpdecker 59607aa9ab Add mustache spec to tests 2014-08-14 00:48:19 -05:00
Kevin Decker 48263be204 Merge pull request #848 from wycats/recursive-partial
Implement partial recursive lookup
2014-08-14 00:48:01 -05:00
kpdecker 9f8110fe15 Implement partial recursive lookup 2014-08-14 00:29:25 -05:00
kpdecker 0edce6e1d1 Inherit compat flag for partials 2014-08-14 00:29:25 -05:00
Kevin Decker d95725d5fd Merge pull request #819 from wycats/compat-mode
Implement recursive field lookup
2014-08-13 21:14:32 -05:00
kpdecker c98613b711 Implement recursive field lookup in compat mode
Provides the mustache behavior of recursive lookup without the use of depthed paths.

Note that this does incur a fairly dramatic performance penalty for depthed queries.
2014-08-13 20:46:42 -05:00
kpdecker 625b00e1da Avoid depth construction overhead for non-depthed 2014-08-13 14:51:33 -05:00
kpdecker b695aaec7c Use depths array rather than passing array args
Approximately doubles the throughput performance of depthed templates and clears the way for doing recursive path lookups in pathed mode.
2014-08-13 14:11:53 -05:00
kpdecker f6dc5ad716 Create contextName helper for context lookup 2014-08-13 11:41:27 -05:00
Kevin Decker 867322adf2 Merge pull request #787 from wycats/standalone-mustaches
Remove whitespace surrounding standalone statements
2014-08-13 09:16:08 -05:00
Kevin Decker 3288f525f3 Merge pull request #846 from espadrine/patch-1
Readme: Use an SVG badge.
2014-08-12 20:49:08 -05:00
kpdecker f2a2914d4f Add additional tests for standalone partials 2014-08-12 15:38:50 -05:00
kpdecker e93d516672 Render indent for standalone partials 2014-08-12 14:41:58 -05:00
kpdecker 2a47d66ee9 Drop omitted content tags from generated output 2014-08-12 14:41:58 -05:00
kpdecker 50c657f138 Flag standalone nodes in the AST 2014-08-12 14:41:58 -05:00
kpdecker 6070179009 Track root status in ProgramNode constructor 2014-08-12 14:41:58 -05:00
kpdecker 4a4dcf1657 Parse context sections by line 2014-08-12 14:41:58 -05:00
kpdecker 6efcaaf8d1 Update to latest saucelabs lib 2014-08-12 14:32:19 -05:00
kpdecker f4af8e06b6 Allow compat flag in test specs 2014-08-11 20:10:12 -05:00
kpdecker 63c5eb7a82 Remove unused JSCompiler.register method 2014-08-11 20:10:12 -05:00
kpdecker 248ad1acae Fix type in README
Fixes #843
2014-08-11 20:08:29 -05:00
kpdecker 905f99b18c Minor test cleanup 2014-08-11 20:08:29 -05:00
Thaddee Tyl 11f85089cc Readme: Use an SVG badge.
Using the up-to-date badge design.
2014-08-08 17:23:38 +02:00
Kevin Decker 5f1bf78555 Merge pull request #833 from mmun/patch-1
Lock mocha dependency to ~1.20.0
2014-07-29 20:38:52 -05:00
Kevin Decker f374939cfd Merge pull request #823 from fractal-surfing/with-inverse
Support inverse sections on the with helper
2014-07-29 09:25:45 -05:00
Martin Muñoz eee2c4d4f2 Refactor blocks, programs and inverses 2014-07-29 02:45:50 -04:00
Martin Muñoz e11c1beb72 Lock mocha dependency to ~1.20.0 2014-07-29 02:22:35 -04:00
Dan Manges 2bdfd2a31d Support inverse sections on the with helper 2014-07-16 19:37:01 +00:00
kpdecker 271106d43f Do not lookup pathed helpers on the helper stack
Fixes #764
2014-07-12 12:50:54 -05:00
kpdecker 1fb7b51ee6 Fix rendering of paths that resolve to zero
Fixes #820
2014-07-12 11:43:45 -05:00
kpdecker c90cfe247c Use ID lookup for ambiguous helper evaluation
Final change for #731
2014-07-07 00:20:02 -05:00
kpdecker 107aa4a66a Add test case for GH-731 2014-07-07 00:01:17 -05:00
Kevin Decker 9a1eda2caf Merge pull request #731 from wycats/path-lookup
Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0
2014-07-06 23:56:33 -05:00
kpdecker b5a5c76ceb Rework lookup null protector logic
- Move the lookup null protection out of `nameLookup` and into that contexts that are aware of the needs for falsy vs. not displayed values.
- Optimize lookup for nested path operations

Fixes #731
2014-07-06 23:41:15 -05:00
kpdecker 4aad72d223 Move lambda resolution to runtime
This has a very positive impact on precompiled output size, particularly for known-helpers cases, and little or no impact on the throughput numbers.
2014-07-06 12:53:01 -05:00
kpdecker 704961b378 Add additional paths to path throughput test 2014-07-06 11:37:24 -05:00
kpdecker 054627adb0 Move precompiler test filter into describe
This allows us to play nicely with in-browser tests
2014-07-05 15:03:18 -05:00
kpdecker 058e7495f9 Add precompiler version FAQ entry.
Resolves #789
2014-07-05 14:45:13 -05:00
kpdecker 059532295d Move precompiler to safer location
Attempts to avoid some of the Node vs. ES6 confusion between the different styles within the application.

Also adds some simple tests in addition to the integration test.
2014-07-05 14:39:28 -05:00
Kevin Decker 0c084399e2 Merge pull request #795 from jwietelmann/master
Turn the precompile script into a wrapper around a module.
2014-07-05 12:30:24 -05:00
Kevin Decker efb17b80a6 Update FAQ styling 2014-07-05 12:22:28 -05:00
kpdecker 7890c7dc89 Add basic FAQ 2014-07-05 12:21:39 -05:00
kpdecker b79e31e5a5 Remove disassemble log statement
Fixes #772
2014-06-28 18:44:11 -07:00
kpdecker 7172d167dd Provide clear throw on {{#each}}
Fixes #773
2014-05-27 10:54:28 -04:00
kpdecker 3cdf14d294 Add test case for Issue #800
This isn’t failing in master but this is a useful test to prevent regressions.
2014-05-27 09:32:29 -04:00
Joel Wietelmann a14c689fa6 Stupid line ending 2014-05-10 14:11:07 -05:00
Joel Wietelmann e143849f8a Knocking the indent back to what it was to make for a less scary-looking pull request 2014-05-10 14:10:05 -05:00
Joel Wietelmann 2e7a3bd702 Turning CLI precompiler into a wrapper around a module. 2014-05-10 14:07:15 -05:00
134 changed files with 9352 additions and 3646 deletions
+129
View File
@@ -0,0 +1,129 @@
module.exports = {
"extends": "eslint:recommended",
"globals": {
"self": false
},
"env": {
"node": 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
},
"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",
// 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": "warn"
},
"parserOptions": {
"sourceType": "module"
}
}
+6
View File
@@ -0,0 +1,6 @@
# Handlebars-template fixtures in test cases need deterministic eol
*.handlebars text eol=lf
*.hbs text eol=lf
# Lexer files as well
*.l text eol=lf
+3
View File
@@ -4,8 +4,11 @@ vendor
lib/handlebars/compiler/parser.js
/dist/
/tmp/
/coverage/
node_modules
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
+3
View File
@@ -0,0 +1,3 @@
[submodule "spec/mustache"]
path = spec/mustache
url = git://github.com/mustache/spec.git
+2
View File
@@ -0,0 +1,2 @@
instrumentation:
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
-51
View File
@@ -1,51 +0,0 @@
{
"predef": [
"console",
"require",
"suite",
"equal",
"equals",
"test",
"testBoth",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module",
"define",
"describe",
"it",
"afterEach",
"beforeEach"
],
"node" : true,
"browser" : true,
"esnext": true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": false,
"eqnull": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": true,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false
}
-23
View File
@@ -1,23 +0,0 @@
.DS_Store
.gitignore
.rvmrc
.jshintrc
.travis.yml
.rspec
Gemfile
Gemfile.lock
Rakefile
Gruntfile.js
*.gemspec
*.nuspec
bench/*
configurations/*
components/*
dist/cdnjs/*
dist/components/*
spec/*
src/*
tasks/*
tmp/*
publish/*
vendor/*
+5 -1
View File
@@ -3,6 +3,7 @@ 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
@@ -13,7 +14,7 @@ env:
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
matrix:
include:
- node_js: '0.10'
- node_js: '10'
env:
- PUBLISH=true
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
@@ -21,3 +22,6 @@ matrix:
cache:
directories:
- node_modules
git:
depth: 100
+115
View File
@@ -0,0 +1,115 @@
# How to Contribute
## Reporting Issues
Please see our [FAQ](https://github.com/wycats/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)
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).
## Branches
* The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
* The branch `master` contains the next version. A release date is not yet specified. Maintainers
should merge the branch `4.x` into the master branch regularly.
## 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)
- Have tests
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
## Building
To build Handlebars.js you'll need a few things installed.
* 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`.
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, you'll want to run `grunt`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
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).
##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
git clone https://github.com/mustache/spec.git mustache
```
From the root directory, run the tests.
```sh
npm test
```
## Ember testing
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
```sh
npm link
grunt build release
cp dist/*.js $emberRepoDir/bower_components/handlebars/
cd $emberRepoDir
npm link handlebars
npm test
```
## Releasing the latest version
*When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch.*
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
A full release may be completed with the following:
```
yo release
npm publish
yo release:publish components handlebars.js dist/components/
cd dist/components/
gem build handlebars-source.gemspec
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)
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.
[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/
+60
View File
@@ -0,0 +1,60 @@
# Frequently Asked Questions
1. 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).
1. 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).
1. 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.
1. 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.
1. 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:
```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.
1. 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:
```sh
handlebars --version
```
If using the integrated precompiler and
```javascript
console.log(Handlebars.VERSION);
```
On the client side.
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.
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).
1. Why doesn't IE like the `default` name in the AMD module?
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.
1. 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.
+84 -46
View File
@@ -1,16 +1,19 @@
var childProcess = require('child_process');
/* eslint-disable no-process-env */
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
eslint: {
options: {
jshintrc: '.jshintrc'
},
files: [
'dist/cjs/**/!(*.min|parser).js'
'*.js',
'bench/**/*.js',
'tasks/**/*.js',
'lib/**/!(*.min|parser).js',
'spec/**/!(*.amd|json2|require).js',
'multi-nodejs-test/*.js'
]
},
@@ -19,8 +22,8 @@ module.exports = function(grunt) {
copy: {
dist: {
options: {
processContent: function(content, path) {
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
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;
}
},
@@ -41,21 +44,16 @@ module.exports = function(grunt) {
}
},
packager: {
global: {
type: 'global',
export: 'Handlebars',
files: [{
cwd: 'lib/',
expand: true,
src: ['handlebars*.js'],
dest: 'dist/'
}]
babel: {
options: {
sourceMaps: 'inline',
loose: ['es6.modules'],
auxiliaryCommentBefore: 'istanbul ignore next'
},
amd: {
type: 'amd',
anonymous: true,
options: {
modules: 'amd'
},
files: [{
expand: true,
cwd: 'lib/',
@@ -65,30 +63,61 @@ module.exports = function(grunt) {
},
cjs: {
type: 'cjs',
options: {
modules: 'common'
},
files: [{
expand: true,
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'
}
},
handlebars: {
entry: './lib/handlebars.js',
output: {
filename: 'handlebars.js'
}
},
runtime: {
entry: './lib/handlebars.runtime.js',
output: {
filename: 'handlebars.runtime.js'
}
}
},
requirejs: {
options: {
optimize: "none",
baseUrl: "dist/amd/"
optimize: 'none',
baseUrl: 'dist/amd/'
},
dist: {
options: {
name: "handlebars",
out: "dist/handlebars.amd.js"
name: 'handlebars',
out: 'dist/handlebars.amd.js'
}
},
runtime: {
options: {
name: "handlebars.runtime",
out: "dist/handlebars.runtime.amd.js"
name: 'handlebars.runtime',
out: 'dist/handlebars.runtime.amd.js'
}
}
},
@@ -97,7 +126,7 @@ module.exports = function(grunt) {
options: {
mangle: true,
compress: true,
preserveComments: 'some'
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
},
dist: {
files: [{
@@ -134,17 +163,25 @@ module.exports = function(grunt) {
build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
detailedError: true,
concurrency: 2,
concurrency: 4,
browsers: [
{browserName: 'chrome'},
{browserName: 'firefox'},
{browserName: 'firefox', version: '3.6'},
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
{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'},
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
{browserName: 'internet explorer', version: 6, platform: 'XP'}
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'}
]
}
},
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'}
]
}
}
@@ -163,18 +200,18 @@ module.exports = function(grunt) {
});
// Build a new version of the library
this.registerTask('build', "Builds a distributable version of the current project", [
this.registerTask('build', 'Builds a distributable version of the current project', [
'eslint',
'parser',
'node',
'globals',
'jshint']);
'globals']);
this.registerTask('amd', ['packager:amd', 'requirejs']);
this.registerTask('node', ['packager:cjs']);
this.registerTask('globals', ['packager:global']);
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', ['amd', 'jshint', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
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');
@@ -182,11 +219,12 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-saucelabs');
grunt.loadNpmTasks('es6-module-packager');
grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks');
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011-2014 by Yehuda Katz
Copyright (C) 2011-2017 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
+28 -329
View File
@@ -1,4 +1,5 @@
[![Travis Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js)
[![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)
Handlebars.js
@@ -10,22 +11,12 @@ Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be.
Checkout the official Handlebars docs site at
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
Installing
----------
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
Alternatively, if you prefer having the latest version of handlebars from
the 'master' branch, passing builds of the 'master' branch are automatically
published to S3. You may download the latest passing master build by grabbing
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
build is published, it is also available as a `handlebars-gitSHA.js` file on
the builds page if you need a version to refer to others.
`handlebars-runtime.js` builds are also available.
**Note**: The S3 builds page is provided as a convenience for the community,
but you should not use it for hosting Handlebars in production.
See our [installation documentation](http://handlebarsjs.com/installation.html).
Usage
-----
@@ -55,258 +46,33 @@ var result = template(data);
// </ul>
```
Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
Registering Helpers
-------------------
You can register helpers that Handlebars will use when evaluating your
template. Here's an example, which assumes that your objects have a URL
embedded in them, as well as the text for a link:
```js
Handlebars.registerHelper('link_to', function() {
return new Handlebars.SafeString("<a href='" + Handlebars.Utils.escapeExpression(this.url) + "'>" + Handlebars.Utils.escapeExpression(this.body) + "</a>");
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = "<ul>{{#posts}}<li>{{link_to}}</li>{{/posts}}</ul>"
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Hello World!</a></li>
// </ul>
```
Helpers take precedence over fields defined on the context. To access a field
that is masked by a helper, a path reference may be used. In the example above
a field named `link_to` on the `context` object would be referenced using:
```
{{./link_to}}
```
Escaping
--------
By default, the `{{expression}}` syntax will escape its contents. This
helps to protect you against accidental XSS problems caused by malicious
data passed from the server as JSON.
To explicitly *not* escape the contents, use the triple-mustache
(`{{{}}}`). You have seen this used in the above example.
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).
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.
### Paths
- [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)
Handlebars.js supports an extended expression syntax that we call paths.
Paths are made up of typical expressions and . characters. Expressions
allow you to not only display data from the current context, but to
display data from contexts that are descendants and ancestors of the
current context.
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.
To display data from descendant contexts, use the `.` character. So, for
example, if your data were structured like:
### Compatibility
```js
var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } };
```
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.
- Alternative delimiters are not supported.
You could display the person's name from the top-level context with the
following expression:
```
{{person.name}}
```
You can backtrack using `../`. For example, if you've already traversed
into the person object you could still display the company's name with
an expression like `{{../company.name}}`, so:
```
{{#with person}}{{name}} - {{../company.name}}{{/person}}
```
would render:
```
Alan - Rad, Inc.
```
### Strings
When calling a helper, you can pass paths or Strings as parameters. For
instance:
```js
Handlebars.registerHelper('link_to', function(title, options) {
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/posts/hello-world'>Post!</a></li>
// </ul>
```
When you pass a String as a parameter to a helper, the literal String
gets passed to the helper function.
### Block Helpers
Handlebars.js also adds the ability to define block helpers. Block
helpers are functions that can be called from anywhere in the template.
Here's an example:
```js
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
Handlebars.registerHelper('link', function(options) {
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
});
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
```
Whenever the block helper is called it is given one or more parameters,
any arguments that are passed in the helper in the call and an `options`
object containing the `fn` function which executes the block's child.
The block's current context may be accessed through `this`.
Block helpers 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.
### Partials
You can register additional templates as partials, which will be used by
Handlebars when it encounters a partial (`{{> partialName}}`). Partials
can either be String templates or compiled template functions. Here's an
example:
```js
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
```
### Comments
You can add comments to your templates with the following syntax:
```js
{{! This is a comment }}
```
You can also use real html comments if you want them to end up in the output.
```html
<div>
{{! This comment will not end up in the output }}
<!-- This comment will show up in the output -->
</div>
```
Precompiling Templates
----------------------
Handlebars allows templates to be precompiled and included as javascript
code rather than the handlebars template allowing for faster startup time.
### Installation
The precompiler script may be installed via npm using the `npm install -g handlebars`
command.
### Usage
<pre>
Precompile handlebar templates.
Usage: handlebars template...
Options:
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
-f, --output Output File [string]
-k, --known Known helpers [string]
-o, --knownOnly Known helpers only [boolean]
-m, --min Minimize output [boolean]
-s, --simple Output template function only. [boolean]
-r, --root Template root. Base value that will be stripped from template names. [string]
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
-n, --namespace Template namespace [string]
-p, --partial Compiling a partial template [boolean]
-d, --data Include data when compiling [boolean]
-e, --extension Template extension. [string]
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
</pre>
If using the precompiler's normal mode, the resulting templates will be
stored to the `Handlebars.templates` object using the relative template
name sans the extension. These templates may be executed in the same
manner as templates.
If using the simple mode the precompiler will generate a single
javascript method. To execute this method it must be passed to the using
the `Handlebars.template` method and the resulting object may be as
normal.
### Optimizations
- Rather than using the full _handlebars.js_ library, implementations that
do not need to compile templates at runtime may include _handlebars.runtime.js_
whose min+gzip size is approximately 1k.
- If a helper is known to exist in the target environment they may be defined
using the `--known name` argument may be used to optimize accesses to these
helpers for size and speed.
- When all helpers are known in advance the `--knownOnly` argument may be used
to optimize all block helper references.
- Implementations that do not use `@data` variables can improve performance of
iteration centric templates by specifying `{data: false}` in the compiler options.
Supported Environments
----------------------
@@ -336,13 +102,7 @@ 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,
and we will have some benchmarks in the near future.
Building
--------
To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
Upgrading
@@ -352,16 +112,9 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea
Known Issues
------------
* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another.
* Handlebars.js can be cryptic when there's an error while rendering.
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
Reporting Issues
----------------
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version.
We also accept [pull requests][pull-request]!
Handlebars in the Wild
----------------------
@@ -369,7 +122,9 @@ 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.
* [Ghost](https://ghost.org/) Just a blogging platform.
@@ -377,6 +132,7 @@ Handlebars in the Wild
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
@@ -387,13 +143,16 @@ Handlebars in the Wild
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
* [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.
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
External Resources
------------------
@@ -402,68 +161,8 @@ External Resources
Have a project using Handlebars? Send us a [pull request][pull-request]!
Helping Out
-----------
To build Handlebars.js you'll need a few things installed.
* Node.js
* [Grunt](http://gruntjs.com/getting-started)
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, you'll want to run `grunt`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
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).
Feel free to contact commondream or wycats through GitHub with any other
questions or feature requests. To submit changes fork the project and
send a pull request.
### Ember testing
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
```sh
grunt build release
export HANDLEBARS_PATH=`pwd`
cd $emberRepoDir
bundle exec rake clean
bundle exec rake test
```
### Releasing
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
A full release may be completed with the following:
```
yo release
npm publish
yo release:publish cdnjs handlebars.js dist/cdnjs/
yo release:publish components handlebars.js dist/components/
cd dist/components/
gem build handlebars-source.gemspec
gem push handlebars-source-*.gem
```
After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution.
License
-------
Handlebars.js is released under the MIT license.
[bower-repo]: https://github.com/components/handlebars.js
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
[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]: http://l.kde.cc/hbs-bug
+37
View File
@@ -0,0 +1,37 @@
# 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
@@ -0,0 +1,14 @@
{
"globals": {
"require": true
},
"rules": {
// Disabling for tests, for now.
"no-path-concat": 0,
"no-var": 0,
"no-shadow": 0,
"handle-callback-err": 0,
"no-console": 0
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
var _ = require('underscore'),
async = require('async'),
var async = require('neo-async'),
fs = require('fs'),
zlib = require('zlib');
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
return;
}
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = {
helpers: {
foo: function(options) {
foo: function() {
return '';
}
},
+5 -5
View File
@@ -1,7 +1,7 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{name}}{{/each}}",
dust: "{#names}{name}{/names}",
mustache: "{{#names}}{{name}}{{/names}}",
eco: "<% for item in @names: %><%= item.name %><% end %>"
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}',
eco: '<% for item in @names: %><%= item.name %><% end %>'
};
+3 -3
View File
@@ -1,4 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#names}}{{name}}{{/names}}"
}
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#names}}{{name}}{{/names}}'
};
+5 -5
View File
@@ -3,13 +3,13 @@ var fs = require('fs');
module.exports = {
context: {
header: function() {
return "Colors";
return 'Colors';
},
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
items: [
{name: "red", current: true, url: "#Red"},
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
{name: 'red', current: true, url: '#Red'},
{name: 'green', current: false, url: '#Green'},
{name: 'blue', current: false, url: '#Blue'}
]
},
+3 -3
View File
@@ -1,4 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
}
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
};
+4 -4
View File
@@ -1,6 +1,6 @@
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 %>"
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 %>'
};
+4 -4
View File
@@ -1,6 +1,6 @@
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 %>"
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 %>'
};
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
return;
}
module.exports[RegExp.$1] = require('./' + RegExp.$1);
+2 -2
View File
@@ -1,4 +1,4 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
context: { person: { name: 'Larry', age: 45 } },
handlebars: '{{#person}}{{name}}{{age}}{{/person}}'
};
+5 -5
View File
@@ -1,7 +1,7 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
dust: "{#person}{name}{age}{/person}",
eco: "<%= @person.name %><%= @person.age %>",
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
context: { person: { name: 'Larry', age: 45 } },
handlebars: '{{#with person}}{{name}}{{age}}{{/with}}',
dust: '{#person}{name}{age}{/person}',
eco: '<%= @person.name %><%= @person.age %>',
mustache: '{{#person}}{{name}}{{age}}{{/person}}'
};
+5 -5
View File
@@ -1,10 +1,10 @@
module.exports = {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
partials: {
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
},
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
dust: "{name}{#kids}{>recursion:./}{/kids}",
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
handlebars: '{{name}}{{#each kids}}{{>recursion}}{{/each}}',
dust: '{name}{#kids}{>recursion:./}{/kids}',
mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}'
};
+6 -6
View File
@@ -1,11 +1,11 @@
module.exports = {
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
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." }
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}}"
handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
dust: '{#peeps}{>variables/}{/peeps}',
mustache: '{{#peeps}}{{>variables}}{{/peeps}}'
};
+5 -5
View File
@@ -1,7 +1,7 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
dust: "{person.name}{person.age}{person.foo}{animal.age}",
eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
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}}'
};
+4 -4
View File
@@ -1,7 +1,7 @@
module.exports = {
context: {},
handlebars: "Hello world",
dust: "Hello world",
mustache: "Hello world",
eco: "Hello world"
handlebars: 'Hello world',
dust: 'Hello world',
mustache: 'Hello world',
eco: 'Hello world'
};
+3 -3
View File
@@ -4,11 +4,11 @@ module.exports = {
return 'foo ' + value;
},
header: function() {
return "Colors";
return 'Colors';
}
},
handlebars: "{{echo (header)}}",
eco: "<%= @echo(@header()) %>"
handlebars: '{{echo (header)}}',
eco: '<%= @echo(@header()) %>'
};
module.exports.context = module.exports.helpers;
+5 -5
View File
@@ -1,8 +1,8 @@
module.exports = {
context: {name: "Mick", count: 30},
handlebars: "Hello {{name}}! You have {{count}} new messages.",
dust: "Hello {name}! You have {count} new messages.",
mustache: "Hello {{name}}! You have {{count}} new messages.",
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
context: {name: 'Mick', count: 30},
handlebars: 'Hello {{name}}! You have {{count}} new messages.',
dust: 'Hello {name}! You have {count} new messages.',
mustache: 'Hello {{name}}! You have {{count}} new messages.',
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
};
+22 -15
View File
@@ -1,23 +1,22 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),
templates = require('./templates'),
eco, dust, Handlebars, Mustache, eco;
eco, dust, Handlebars, Mustache;
try {
dust = require("dustjs-linkedin");
dust = require('dustjs-linkedin');
} catch (err) { /* NOP */ }
try {
Mustache = require("mustache");
Mustache = require('mustache');
} catch (err) { /* NOP */ }
try {
eco = require("eco");
eco = require('eco');
} catch (err) { /* NOP */ }
function error() {
throw new Error("EWOT");
throw new Error('EWOT');
}
function makeSuite(bench, name, template, handlebarsOnly) {
@@ -28,21 +27,28 @@ function makeSuite(bench, name, template, handlebarsOnly) {
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, name) {
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
_.each(template.partials && template.partials.handlebars, function(partial, partialName) {
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
});
handlebarsOut = handlebar(context, options);
bench("handlebars", function() {
bench('handlebars', function() {
handlebar(context, options);
});
compatOut = compat(context, options);
bench('compat', function() {
compat(context, options);
});
if (handlebarsOnly) {
return;
}
@@ -54,8 +60,8 @@ function makeSuite(bench, name, template, handlebarsOnly) {
dust.render(templateName, context, function(err, out) { dustOut = out; });
bench("dust", function() {
dust.render(templateName, context, function(err, out) { });
bench('dust', function() {
dust.render(templateName, context, function() {});
});
} else {
bench('dust', error);
@@ -68,11 +74,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
ecoOut = ecoTemplate(context);
bench("eco", function() {
bench('eco', function() {
ecoTemplate(context);
});
} else {
bench("eco", error);
bench('eco', error);
}
}
@@ -83,11 +89,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
bench("mustache", function() {
bench('mustache', function() {
Mustache.to_html(mustacheSource, context, mustachePartials);
});
} else {
bench("mustache", error);
bench('mustache', error);
}
}
@@ -107,6 +113,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
}
}
compare(compatOut, 'compat');
compare(dustOut, 'dust');
compare(ecoOut, 'eco');
compare(mustacheOut, 'mustache');
+33 -35
View File
@@ -1,7 +1,7 @@
var _ = require('underscore'),
Benchmark = require("benchmark");
Benchmark = require('benchmark');
var BenchWarmer = function(names) {
function BenchWarmer() {
this.benchmarks = [];
this.currentBenches = [];
this.names = [];
@@ -9,9 +9,9 @@ var BenchWarmer = function(names) {
this.minimum = Infinity;
this.maximum = -Infinity;
this.errors = {};
};
}
var print = require("sys").print;
var print = require('util').print;
BenchWarmer.prototype = {
winners: function(benches) {
@@ -29,7 +29,7 @@ BenchWarmer.prototype = {
});
},
push: function(name, fn) {
if(this.names.indexOf(name) == -1) {
if (this.names.indexOf(name) == -1) {
this.names.push(name);
}
@@ -37,9 +37,9 @@ BenchWarmer.prototype = {
this.first = false;
var bench = new Benchmark(fn, {
name: this.suiteName + ": " + name,
name: this.suiteName + ': ' + name,
onComplete: function() {
if(first) { self.startLine(suiteName); }
if (first) { self.startLine(suiteName); }
self.writeBench(bench);
self.currentBenches.push(bench);
}, onError: function() {
@@ -58,7 +58,7 @@ BenchWarmer.prototype = {
this.printHeader('ops/msec', true);
Benchmark.invoke(this.benchmarks, {
name: "run",
name: 'run',
onComplete: function() {
self.scaleTimes();
@@ -76,7 +76,7 @@ BenchWarmer.prototype = {
print('\n');
var errors = false, prop, bench;
for(prop in self.errors) {
for (prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
errors = true;
@@ -84,18 +84,18 @@ BenchWarmer.prototype = {
}
}
if(errors) {
print("\n\nErrors:\n");
for(prop in self.errors) {
if (errors) {
print('\n\nErrors:\n');
for (prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
bench = self.errors[prop];
print("\n" + bench.name + ":\n");
print('\n' + bench.name + ':\n');
print(bench.error.message);
if(bench.error.stack) {
print(bench.error.stack.join("\n"));
if (bench.error.stack) {
print(bench.error.stack.join('\n'));
}
print("\n");
print('\n');
}
}
}
@@ -104,7 +104,7 @@ BenchWarmer.prototype = {
}
});
print("\n");
print('\n');
},
scaleTimes: function() {
@@ -121,10 +121,10 @@ BenchWarmer.prototype = {
printHeader: function(title, winners) {
var benchSize = 0, names = this.names, i, l;
for(i=0, l=names.length; i<l; i++) {
for (i = 0, l = names.length; i < l; i++) {
var name = names[i];
if(benchSize < name.length) { benchSize = name.length; }
if (benchSize < name.length) { benchSize = name.length; }
}
this.nameSize = benchSize + 2;
@@ -133,28 +133,28 @@ BenchWarmer.prototype = {
this.startLine(title);
horSize = horSize + this.benchSize;
for(i=0, l=names.length; i<l; i++) {
for (i = 0, l = names.length; i < l; i++) {
this.writeValue(names[i]);
horSize = horSize + this.benchSize;
}
if (winners) {
print("WINNER(S)");
horSize = horSize + "WINNER(S)".length;
print('WINNER(S)');
horSize = horSize + 'WINNER(S)'.length;
}
print("\n" + new Array(horSize + 1).join("-"));
print('\n' + new Array(horSize + 1).join('-'));
},
startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
return bench.name.split(": ")[1];
return bench.name.split(': ')[1];
});
this.currentBenches = [];
print(winners.join(", "));
print("\n");
print(winners.join(', '));
print('\n');
if (name) {
this.writeValue(name);
@@ -163,9 +163,9 @@ BenchWarmer.prototype = {
writeBench: function(bench) {
var out;
if(!bench.error) {
if (!bench.error) {
var count = bench.hz,
moe = count * bench.stats.rme / 100,
moe = count * bench.stats.rme / 100,
minimum,
maximum;
@@ -174,24 +174,22 @@ BenchWarmer.prototype = {
minimum = count - moe;
maximum = count + moe;
out = count + " ±" + moe + " (" + bench.cycles + ")";
out = count + ' ±' + moe + ' (' + bench.cycles + ')';
this.times[bench.suiteName][bench.benchName] = count;
this.minimum = Math.min(this.minimum, minimum);
this.maximum = Math.max(this.maximum, maximum);
} else if (bench.error.message === 'EWOT') {
out = 'NA';
} else {
if (bench.error.message === 'EWOT') {
out = 'NA';
} else {
out = 'E';
}
out = 'E';
}
this.writeValue(out);
},
writeValue: function(out) {
var padding = this.benchSize - out.length + 1;
out = out + new Array(padding).join(" ");
out = out + new Array(padding).join(' ');
print(out);
}
};
+3 -1
View File
@@ -22,6 +22,8 @@ module.exports = function(grunt, makeSuite, callback) {
});
warmer.bench(function() {
callback && callback(warmer.times, warmer.scaled);
if (callback) {
callback(warmer.times, warmer.scaled);
}
});
};
+34 -141
View File
@@ -1,12 +1,16 @@
#!/usr/bin/env node
var optimist = require('optimist')
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
.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)',
@@ -22,7 +26,7 @@ var optimist = require('optimist')
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
'default': ''
},
'k': {
'type': 'string',
@@ -50,17 +54,27 @@ var optimist = require('optimist')
'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' : {
'p': {
'type': 'boolean',
'description': 'Compiling a partial template',
'alias': 'partial'
},
'd' : {
'd': {
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
@@ -80,156 +94,35 @@ var optimist = require('optimist')
'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 template = [0];
if (!argv._.length) {
throw 'Must define at least one template or directory.';
}
argv._.forEach(function(template) {
try {
fs.statSync(template);
} catch (err) {
throw 'Unable to open template file "' + template + '"';
}
});
})
.check(function(argv) {
if (argv.simple && argv.min) {
throw 'Unable to minimze simple output';
}
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
throw 'Unable to output multiple templates in simple mode';
}
});
var fs = require('fs'),
handlebars = require('../lib'),
basename = require('path').basename,
uglify = require('uglify-js');
var argv = optimist.argv,
template = argv._[0];
var argv = optimist.argv;
argv.files = argv._;
delete argv._;
if (argv.version) {
return console.log(handlebars.VERSION);
}
// Convert the known list into a hash
var known = {};
if (argv.known && !Array.isArray(argv.known)) {
argv.known = [argv.known];
}
if (argv.known) {
for (var i = 0, len = argv.known.length; i < len; i++) {
known[argv.known[i]] = true;
var Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function(err, opts) {
if (err) {
throw err;
}
}
// Build file extension pattern
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
extension = new RegExp('\\.' + extension + '$');
var output = [];
if (!argv.simple) {
if (argv.amd) {
output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
} else if (argv.commonjs) {
output.push('var Handlebars = require("' + argv.commonjs + '");');
if (opts.help || (!opts.templates.length && !opts.version)) {
optimist.showHelp();
} else {
output.push('(function() {\n');
Precompiler.cli(opts);
}
output.push(' var template = Handlebars.template, templates = ');
output.push(argv.namespace);
output.push(' = ');
output.push(argv.namespace);
output.push(' || {};\n');
}
function processTemplate(template, root, explicit) {
var path = template,
stat = fs.statSync(path);
if (stat.isDirectory()) {
fs.readdirSync(template).map(function(file) {
var path = template + '/' + file;
if (extension.test(path) || fs.statSync(path).isDirectory()) {
processTemplate(path, root || template);
}
});
} else if (explicit || extension.test(path)) {
var data = fs.readFileSync(path, 'utf8');
if (argv.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
var options = {
knownHelpers: known,
knownHelpersOnly: argv.o
};
if (argv.data) {
options.data = true;
}
// Clean the template name
if (!root) {
template = basename(template);
} else if (template.indexOf(root) === 0) {
template = template.substring(root.length+1);
}
template = template.replace(extension, '');
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
}
}
argv._.forEach(function(template) {
processTemplate(template, argv.root, true);
});
// Output the content
if (!argv.simple) {
if (argv.amd) {
if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
if(argv.partial){
output.push('return Handlebars.partials;\n');
} else {
output.push('return templates;\n');
}
}
output.push('});');
} else if (!argv.commonjs) {
output.push('})();');
}
}
output = output.join('');
if (argv.min) {
output = uglify.minify(output, {fromString: true}).code;
}
if (argv.output) {
fs.writeFileSync(argv.output, output, 'utf8');
} else {
console.log(output);
}
+2 -1
View File
@@ -1,6 +1,7 @@
{
"name": "handlebars",
"version": "2.0.0-alpha.4",
"version": "4.1.1",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
}
+1 -1
View File
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
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.version = package["version"]
gem.version = package["version"].sub "-", "."
gem.license = "MIT"
gem.files = [
+1 -1
View File
@@ -2,7 +2,7 @@
<package>
<metadata>
<id>handlebars.js</id>
<version>2.0.0-alpha.4</version>
<version>4.1.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>
+20
View File
@@ -0,0 +1,20 @@
{
"name": "handlebars",
"version": "4.1.1",
"license": "MIT",
"jspm": {
"main": "handlebars",
"shim": {
"handlebars": {
"exports": "Handlebars"
}
},
"files": [
"handlebars.js",
"handlebars.runtime.js"
],
"buildConfig": {
"minify": true
}
}
}
+338
View File
@@ -0,0 +1,338 @@
# Handlebars Compiler APIs
There are a number of formal APIs that tool implementors may interact with.
## AST
Other tools may interact with the formal AST as defined below. Any JSON structure matching this pattern may be used and passed into the `compile` and `precompile` methods in the same way as the text for a template.
AST structures may be generated either with the `Handlebars.parse` method and then manipulated, via the `Handlebars.AST` objects of the same name, or constructed manually as a generic JavaScript object matching the structure defined below.
```javascript
var ast = Handlebars.parse(myTemplate);
// Modify ast
Handlebars.precompile(ast);
```
### Basic
```java
interface Node {
type: string;
loc: SourceLocation | null;
}
interface SourceLocation {
source: string | null;
start: Position;
end: Position;
}
interface Position {
line: uint >= 1;
column: uint >= 0;
}
```
### Programs
```java
interface Program <: Node {
type: "Program";
body: [ Statement ];
blockParams: [ string ];
}
```
### Statements
```java
interface Statement <: Node { }
interface MustacheStatement <: Statement {
type: "MustacheStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
escaped: boolean;
strip: StripFlags | null;
}
interface BlockStatement <: Statement {
type: "BlockStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
inverse: Program | null;
openStrip: StripFlags | null;
inverseStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
interface PartialStatement <: Statement {
type: "PartialStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
indent: string;
strip: StripFlags | null;
}
interface PartialBlockStatement <: Statement {
type: "PartialBlockStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
program: Program | null;
indent: string;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
`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";
value: string;
original: string;
}
interface CommentStatement <: Statement {
type: "CommentStatement";
value: string;
strip: StripFlags | null;
}
```
```java
interface Decorator <: Statement {
type: "Decorator";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
strip: StripFlags | null;
}
interface DecoratorBlock <: Statement {
type: "DecoratorBlock";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
### Expressions
```java
interface Expression <: Node { }
```
##### SubExpressions
```java
interface SubExpression <: Expression {
type: "SubExpression";
path: PathExpression;
params: [ Expression ];
hash: Hash;
}
```
##### Paths
```java
interface PathExpression <: Expression {
type: "PathExpression";
data: boolean;
depth: uint >= 0;
parts: [ string ];
original: string;
}
```
- `data` is true when the given expression is a `@data` reference.
- `depth` is an integer representation of which context the expression references. `0` represents the current context, `1` would be `../`, etc.
- `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
interface Literal <: Expression { }
interface StringLiteral <: Literal {
type: "StringLiteral";
value: string;
original: string;
}
interface BooleanLiteral <: Literal {
type: "BooleanLiteral";
value: boolean;
original: boolean;
}
interface NumberLiteral <: Literal {
type: "NumberLiteral";
value: number;
original: number;
}
interface UndefinedLiteral <: Literal {
type: "UndefinedLiteral";
}
interface NullLiteral <: Literal {
type: "NullLiteral";
}
```
### Miscellaneous
```java
interface Hash <: Node {
type: "Hash";
pairs: [ HashPair ];
}
interface HashPair <: Node {
type: "HashPair";
key: string;
value: Expression;
}
interface StripFlags {
open: boolean;
close: boolean;
}
```
`StripFlags` are used to signify whitespace control character that may have been entered on a given statement.
## AST Visitor
`Handlebars.Visitor` is available as a base class for general interaction with AST structures. This will by default traverse the entire tree and individual methods may be overridden to provide specific responses to particular nodes.
Recording all referenced partial names:
```javascript
var Visitor = Handlebars.Visitor;
function ImportScanner() {
this.partials = [];
}
ImportScanner.prototype = new Visitor();
ImportScanner.prototype.PartialStatement = function(partial) {
this.partials.push({request: partial.name.original});
Visitor.prototype.PartialStatement.call(this, partial);
};
var scanner = new ImportScanner();
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.
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.
## JavaScript Compiler
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.
- `parent` is the existing code in the path resolution
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
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.
- `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.
- `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.
### Example for the compiler api.
This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change.
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);
}
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
// Use this compile to compile BlockStatment-Blocks
MyCompiler.prototype.compiler = MyCompiler
MyCompiler.prototype.nameLookup = function(parent, name, type) {
if (type === 'context') {
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
} else {
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.JavaScriptCompiler = MyCompiler;
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
console.log(template({
test: [
{value: 'a'},
{value: 'b'},
{value: 'c'}
]
}));
```
+19
View File
@@ -0,0 +1,19 @@
# 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 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.
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
- `program`: The block to wrap
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
- `container`: The current runtime container
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
- `data`: The current `@data` values
- `blockParams`: The current block parameters stack
- `depths`: The current context stack
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.
+356
View File
@@ -0,0 +1,356 @@
/* These definitions were imported from https://github.com/DefinitelyTyped/DefinitelyTyped
* and includes previous contributions from the DefinitelyTyped community by:
* - Albert Willemsen <https://github.com/AlbertWillemsen-Centric>
* - Boris Yankov <https://github.com/borisyankov>
* - Jessica Franco <https://github.com/Kovensky>
* - Masahiro Wakame <https://github.com/vvakame>
* - Raanan Weber <https://github.com/RaananW>
* - Sergei Dorogin <https://github.com/evil-shrike>
* - webbiesdk <https://github.com/webbiesdk>
* For full history prior to their migration to handlebars.js, please see:
* https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/handlebars
*/
declare namespace Handlebars {
export interface TemplateDelegate<T = any> {
(context: T, options?: RuntimeOptions): string;
}
export type Template<T = any> = TemplateDelegate<T>|string;
export interface RuntimeOptions {
partial?: boolean;
depths?: any[];
helpers?: { [name: string]: Function };
partials?: { [name: string]: HandlebarsTemplateDelegate };
decorators?: { [name: string]: Function };
data?: any;
blockParams?: any[];
}
export interface HelperOptions {
fn: TemplateDelegate;
inverse: TemplateDelegate;
hash: any;
data?: any;
}
export interface HelperDelegate {
(context?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any, arg5?: any, options?: HelperOptions): any;
}
export interface HelperDeclareSpec {
[key: string]: HelperDelegate;
}
export interface ParseOptions {
srcName?: string,
ignoreStandalone?: boolean
}
export function registerHelper(name: string, fn: HelperDelegate): void;
export function registerHelper(name: HelperDeclareSpec): void;
export function unregisterHelper(name: string): void;
export function registerPartial(name: string, fn: Template): void;
export function registerPartial(spec: { [name: string]: HandlebarsTemplateDelegate }): void;
export function unregisterPartial(name: string): void;
// TODO: replace Function with actual signature
export function registerDecorator(name: string, fn: Function): void;
export function unregisterDecorator(name: string): void;
export function K(): void;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function Exception(message: string): void;
export function log(level: number, obj: any): void;
export function parse(input: string, options?: ParseOptions): hbs.AST.Program;
export function compile<T = any>(input: any, options?: CompileOptions): HandlebarsTemplateDelegate<T>;
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
export function template<T = any>(precompilation: TemplateSpecification): HandlebarsTemplateDelegate<T>;
export function create(): typeof Handlebars;
export const escapeExpression: typeof Utils.escapeExpression;
//export const Utils: typeof hbs.Utils;
export const logger: Logger;
export const templates: HandlebarsTemplates;
export const helpers: { [name: string]: HelperDelegate };
export const partials: { [name: string]: any };
// TODO: replace Function with actual signature
export const decorators: { [name: string]: Function };
export function noConflict(): typeof Handlebars;
export class SafeString {
constructor(str: string);
toString(): string;
toHTML(): string;
}
export namespace Utils {
export function escapeExpression(str: string): string;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function isEmpty(obj: any) : boolean;
export function extend(obj: any, ...source: any[]): any;
export function toString(obj: any): string;
export function isArray(obj: any): boolean;
export function isFunction(obj: any): boolean;
}
export namespace AST {
export const helpers: hbs.AST.helpers;
}
interface ICompiler {
accept(node: hbs.AST.Node): void;
Program(program: hbs.AST.Program): void;
BlockStatement(block: hbs.AST.BlockStatement): void;
PartialStatement(partial: hbs.AST.PartialStatement): void;
PartialBlockStatement(partial: hbs.AST.PartialBlockStatement): void;
DecoratorBlock(decorator: hbs.AST.DecoratorBlock): void;
Decorator(decorator: hbs.AST.Decorator): void;
MustacheStatement(mustache: hbs.AST.MustacheStatement): void;
ContentStatement(content: hbs.AST.ContentStatement): void;
CommentStatement(comment?: hbs.AST.CommentStatement): void;
SubExpression(sexpr: hbs.AST.SubExpression): void;
PathExpression(path: hbs.AST.PathExpression): void;
StringLiteral(str: hbs.AST.StringLiteral): void;
NumberLiteral(num: hbs.AST.NumberLiteral): void;
BooleanLiteral(bool: hbs.AST.BooleanLiteral): void;
UndefinedLiteral(): void;
NullLiteral(): void;
Hash(hash: hbs.AST.Hash): void;
}
export class Visitor implements ICompiler {
accept(node: hbs.AST.Node): void;
acceptKey(node: hbs.AST.Node, name: string): void;
acceptArray(arr: hbs.AST.Expression[]): void;
Program(program: hbs.AST.Program): void;
BlockStatement(block: hbs.AST.BlockStatement): void;
PartialStatement(partial: hbs.AST.PartialStatement): void;
PartialBlockStatement(partial: hbs.AST.PartialBlockStatement): void;
DecoratorBlock(decorator: hbs.AST.DecoratorBlock): void;
Decorator(decorator: hbs.AST.Decorator): void;
MustacheStatement(mustache: hbs.AST.MustacheStatement): void;
ContentStatement(content: hbs.AST.ContentStatement): void;
CommentStatement(comment?: hbs.AST.CommentStatement): void;
SubExpression(sexpr: hbs.AST.SubExpression): void;
PathExpression(path: hbs.AST.PathExpression): void;
StringLiteral(str: hbs.AST.StringLiteral): void;
NumberLiteral(num: hbs.AST.NumberLiteral): void;
BooleanLiteral(bool: hbs.AST.BooleanLiteral): void;
UndefinedLiteral(): void;
NullLiteral(): void;
Hash(hash: hbs.AST.Hash): void;
}
}
/**
* Implement this interface on your MVW/MVVM/MVC views such as Backbone.View
**/
interface HandlebarsTemplatable {
template: HandlebarsTemplateDelegate;
}
// NOTE: for backward compatibility of this typing
type HandlebarsTemplateDelegate<T = any> = Handlebars.TemplateDelegate<T>;
interface HandlebarsTemplates {
[index: string]: HandlebarsTemplateDelegate;
}
interface TemplateSpecification {
}
// for backward compatibility of this typing
type RuntimeOptions = Handlebars.RuntimeOptions;
interface CompileOptions {
data?: boolean;
compat?: boolean;
knownHelpers?: {
helperMissing?: boolean;
blockHelperMissing?: boolean;
each?: boolean;
if?: boolean;
unless?: boolean;
with?: boolean;
log?: boolean;
lookup?: boolean;
};
knownHelpersOnly?: boolean;
noEscape?: boolean;
strict?: boolean;
assumeObjects?: boolean;
preventIndent?: boolean;
ignoreStandalone?: boolean;
explicitPartialContext?: boolean;
}
interface PrecompileOptions extends CompileOptions {
srcName?: string;
destName?: string;
}
declare namespace hbs {
// for backward compatibility of this typing
type SafeString = Handlebars.SafeString;
type Utils = typeof Handlebars.Utils;
}
interface Logger {
DEBUG: number;
INFO: number;
WARN: number;
ERROR: number;
level: number;
methodMap: { [level: number]: string };
log(level: number, obj: string): void;
}
declare namespace hbs {
namespace AST {
interface Node {
type: string;
loc: SourceLocation;
}
interface SourceLocation {
source: string;
start: Position;
end: Position;
}
interface Position {
line: number;
column: number;
}
interface Program extends Node {
body: Statement[];
blockParams: string[];
}
interface Statement extends Node {}
interface MustacheStatement extends Statement {
path: PathExpression | Literal;
params: Expression[];
hash: Hash;
escaped: boolean;
strip: StripFlags;
}
interface Decorator extends MustacheStatement { }
interface BlockStatement extends Statement {
path: PathExpression;
params: Expression[];
hash: Hash;
program: Program;
inverse: Program;
openStrip: StripFlags;
inverseStrip: StripFlags;
closeStrip: StripFlags;
}
interface DecoratorBlock extends BlockStatement { }
interface PartialStatement extends Statement {
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
indent: string;
strip: StripFlags;
}
interface PartialBlockStatement extends Statement {
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
program: Program;
openStrip: StripFlags;
closeStrip: StripFlags;
}
interface ContentStatement extends Statement {
value: string;
original: StripFlags;
}
interface CommentStatement extends Statement {
value: string;
strip: StripFlags;
}
interface Expression extends Node {}
interface SubExpression extends Expression {
path: PathExpression;
params: Expression[];
hash: Hash;
}
interface PathExpression extends Expression {
data: boolean;
depth: number;
parts: string[];
original: string;
}
interface Literal extends Expression {}
interface StringLiteral extends Literal {
value: string;
original: string;
}
interface BooleanLiteral extends Literal {
value: boolean;
original: boolean;
}
interface NumberLiteral extends Literal {
value: number;
original: number;
}
interface UndefinedLiteral extends Literal {}
interface NullLiteral extends Literal {}
interface Hash extends Node {
pairs: HashPair[];
}
interface HashPair extends Node {
key: string;
value: Expression;
}
interface StripFlags {
open: boolean;
close: boolean;
}
interface helpers {
helperExpression(node: Node): boolean;
scopeId(path: PathExpression): boolean;
simpleId(path: PathExpression): boolean;
}
}
}
declare module "handlebars" {
export = Handlebars;
}
declare module "handlebars/runtime" {
export = Handlebars;
}
+22 -14
View File
@@ -1,20 +1,22 @@
/*globals Handlebars: true */
import Handlebars from "./handlebars.runtime";
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 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';
var _create = Handlebars.create;
var create = function() {
var hb = _create();
import noConflict from './handlebars/no-conflict';
let _create = runtime.create;
function create() {
let hb = _create();
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,9 +27,15 @@ var create = function() {
hb.parse = parse;
return hb;
};
}
Handlebars = create();
Handlebars.create = create;
let inst = create();
inst.create = create;
export default Handlebars;
noConflict(inst);
inst.Visitor = Visitor;
inst['default'] = inst;
export default inst;
+18 -12
View File
@@ -1,21 +1,23 @@
/*globals Handlebars: true */
module base from "./handlebars/base";
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";
module Utils from "./handlebars/utils";
module runtime from "./handlebars/runtime";
import SafeString from './handlebars/safe-string';
import Exception from './handlebars/exception';
import * as Utils from './handlebars/utils';
import * as runtime from './handlebars/runtime';
import noConflict from './handlebars/no-conflict';
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() {
var hb = new base.HandlebarsEnvironment();
function create() {
let hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.escapeExpression = Utils.escapeExpression;
hb.VM = runtime;
hb.template = function(spec) {
@@ -23,9 +25,13 @@ var create = function() {
};
return hb;
};
}
var Handlebars = create();
Handlebars.create = create;
let inst = create();
inst.create = create;
export default Handlebars;
noConflict(inst);
inst['default'] = inst;
export default inst;
+39 -191
View File
@@ -1,41 +1,44 @@
module Utils from "./utils";
import Exception from "./exception";
import {createFrame, extend, toString} from './utils';
import Exception from './exception';
import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import logger from './logger';
export var VERSION = "2.0.0-alpha.4";
export var COMPILER_REVISION = 5;
export const VERSION = '4.1.1';
export const COMPILER_REVISION = 7;
export var REVISION_CHANGES = {
export const REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '== 1.x.x',
5: '>= 2.0.0'
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1',
7: '>= 4.0.0'
};
var isArray = Utils.isArray,
isFunction = Utils.isFunction,
toString = Utils.toString,
objectType = '[object Object]';
const objectType = '[object Object]';
export function HandlebarsEnvironment(helpers, partials) {
export function HandlebarsEnvironment(helpers, partials, decorators) {
this.helpers = helpers || {};
this.partials = partials || {};
this.decorators = decorators || {};
registerDefaultHelpers(this);
registerDefaultDecorators(this);
}
HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
logger: logger,
log: log,
log: logger.log,
registerHelper: function(name, fn, inverse) {
registerHelper: function(name, fn) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
Utils.extend(this.helpers, name);
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
},
@@ -43,188 +46,33 @@ HandlebarsEnvironment.prototype = {
delete this.helpers[name];
},
registerPartial: function(name, str) {
registerPartial: function(name, partial) {
if (toString.call(name) === objectType) {
Utils.extend(this.partials, name);
extend(this.partials, name);
} else {
this.partials[name] = str;
if (typeof partial === 'undefined') {
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
}
this.partials[name] = partial;
}
},
unregisterPartial: function(name) {
delete this.partials[name];
},
registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function(name) {
delete this.decorators[name];
}
};
function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
if(arguments.length === 1) {
// A missing field in a {{foo}} constuct.
return undefined;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
}
});
export let log = logger.log;
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
if (isFunction(context)) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
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) {
var data = createFrame(options.data);
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
options = {data: data};
}
return fn(context, options);
}
});
instance.registerHelper('each', function(context, options) {
// Allow for {{#each}}
if (!options) {
options = context;
context = this;
}
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var contextPath;
if (options.data && options.ids) {
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
data.index = i;
data.first = (i === 0);
data.last = (i === (context.length-1));
if (contextPath) {
data.contextPath = contextPath + i;
}
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) {
data.key = key;
data.index = i;
data.first = (i === 0);
if (contextPath) {
data.contextPath = contextPath + key;
}
}
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
instance.registerHelper('if', function(conditional, options) {
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
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
var fn = options.fn;
if (!Utils.isEmpty(context)) {
if (options.data && options.ids) {
var data = createFrame(options.data);
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
options = {data:data};
}
return fn(context, options);
}
});
instance.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
instance.log(level, context);
});
instance.registerHelper('lookup', function(obj, field, options) {
return obj && obj[field];
});
}
export var logger = {
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
// State enum
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
level: 3,
// can be overridden in the host environment
log: function(level, obj) {
if (logger.level <= level) {
var method = logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
export function log(level, obj) { logger.log(level, obj); }
export var createFrame = function(object) {
var frame = Utils.extend({}, object);
frame._parent = object;
return frame;
};
export {createFrame, logger};
+16 -234
View File
@@ -1,246 +1,28 @@
import Exception from "../exception";
function LocationInfo(locInfo){
locInfo = locInfo || {};
this.firstLine = locInfo.first_line;
this.firstColumn = locInfo.first_column;
this.lastColumn = locInfo.last_column;
this.lastLine = locInfo.last_line;
}
var AST = {
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
var inverseLocationInfo, firstInverseNode;
if (arguments.length === 3) {
locInfo = inverse;
inverse = null;
} else if (arguments.length === 2) {
locInfo = inverseStrip;
inverseStrip = null;
}
LocationInfo.call(this, locInfo);
this.type = "program";
this.statements = statements;
this.strip = {};
if(inverse) {
firstInverseNode = inverse[0];
if (firstInverseNode) {
inverseLocationInfo = {
first_line: firstInverseNode.firstLine,
last_line: firstInverseNode.lastLine,
last_column: firstInverseNode.lastColumn,
first_column: firstInverseNode.firstColumn
};
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
} else {
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
}
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
},
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "mustache";
this.strip = strip;
// Open may be a string parsed from the parser or a passed boolean flag
if (open != null && open.charAt) {
// Must use charAt to support IE pre-10
var escapeFlag = open.charAt(3) || open.charAt(2);
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
} else {
this.escaped = !!open;
}
if (rawParams instanceof AST.SexprNode) {
this.sexpr = rawParams;
} else {
// Support old AST API
this.sexpr = new AST.SexprNode(rawParams, hash);
}
this.sexpr.isRoot = true;
// Support old AST API that stored this info in MustacheNode
this.id = this.sexpr.id;
this.params = this.sexpr.params;
this.hash = this.sexpr.hash;
this.eligibleHelper = this.sexpr.eligibleHelper;
this.isHelper = this.sexpr.isHelper;
},
SexprNode: function(rawParams, hash, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "sexpr";
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
let AST = {
// Public API used to evaluate derived attributes regarding AST nodes
helpers: {
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = !!(params.length || hash);
helperExpression: function(node) {
return (node.type === 'SubExpression')
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
&& !!((node.params && node.params.length) || node.hash));
},
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
this.eligibleHelper = this.isHelper || id.isSimple;
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
},
PartialNode: function(partialName, context, hash, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "partial";
this.partialName = partialName;
this.context = context;
this.hash = hash;
this.strip = strip;
},
BlockNode: function(mustache, program, inverse, close, locInfo) {
LocationInfo.call(this, locInfo);
if(mustache.sexpr.id.original !== close.path.original) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
}
this.type = 'block';
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
this.strip = {
left: mustache.strip.left,
right: close.strip.right
};
(program || inverse).strip.left = mustache.strip.right;
(inverse || program).strip.right = close.strip.left;
if (inverse && !program) {
this.isInverse = true;
}
},
RawBlockNode: function(mustache, content, close, locInfo) {
LocationInfo.call(this, locInfo);
if (mustache.sexpr.id.original !== close) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
}
content = new AST.ContentNode(content, locInfo);
this.type = 'block';
this.mustache = mustache;
this.program = new AST.ProgramNode([content], locInfo);
},
ContentNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "content";
this.string = string;
},
HashNode: function(pairs, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "hash";
this.pairs = pairs;
},
IdNode: function(parts, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "ID";
var original = "",
dig = [],
depth = 0,
depthString = '';
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i].part;
original += (parts[i].separator || '') + part;
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) {
throw new Exception("Invalid path: " + original, this);
} else if (part === "..") {
depth++;
depthString += '../';
} else {
this.isScoped = true;
}
} else {
dig.push(part);
}
}
this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.idName = depthString + this.string;
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`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
this.stringModeValue = this.string;
},
PartialNameNode: function(name, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "PARTIAL_NAME";
this.name = name.original;
},
DataNode: function(id, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "DATA";
this.id = id;
this.stringModeValue = id.stringModeValue;
this.idName = '@' + id.stringModeValue;
},
StringNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "STRING";
this.original =
this.string =
this.stringModeValue = string;
},
NumberNode: function(number, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "NUMBER";
this.original =
this.number = number;
this.stringModeValue = Number(number);
},
BooleanNode: function(bool, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
},
CommentNode: function(comment, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "comment";
this.comment = comment;
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
// most modify the object to operate properly.
// must modify the object to operate properly.
export default AST;
+19 -7
View File
@@ -1,12 +1,24 @@
import parser from "./parser";
import AST from "./ast";
import parser from './parser';
import WhitespaceControl from './whitespace-control';
import * as Helpers from './helpers';
import { extend } from '../utils';
export { parser };
export function parse(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === AST.ProgramNode) { return input; }
let yy = {};
extend(yy, Helpers);
parser.yy = AST;
return parser.parse(input);
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));
}
+168
View File
@@ -0,0 +1,168 @@
/* global define */
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
// they are running on the browser and thus have no need for the source-map library.
let SourceMap = require('source-map');
SourceNode = SourceMap.SourceNode;
}
} catch (err) {
/* NOP */
}
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
if (!SourceNode) {
SourceNode = function(line, column, srcFile, chunks) {
this.src = '';
if (chunks) {
this.add(chunks);
}
};
/* istanbul ignore next */
SourceNode.prototype = {
add: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src += chunks;
},
prepend: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src = chunks + this.src;
},
toStringWithSourceMap: function() {
return {code: this.toString()};
},
toString: function() {
return this.src;
}
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
for (let i = 0, len = chunk.length; i < len; i++) {
ret.push(codeGen.wrap(chunk[i], loc));
}
return ret;
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
// Handle primitives that the SourceNode will throw up on
return chunk + '';
}
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
}
CodeGen.prototype = {
isEmpty() {
return !this.source.length;
},
prepend: function(source, loc) {
this.source.unshift(this.wrap(source, loc));
},
push: function(source, loc) {
this.source.push(this.wrap(source, loc));
},
merge: function() {
let source = this.empty();
this.each(function(line) {
source.add([' ', line, '\n']);
});
return source;
},
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: {}};
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
},
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);
},
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') + '"';
},
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]);
}
}
}
let ret = this.generateList(pairs);
ret.prepend('{');
ret.add('}');
return ret;
},
generateList: function(entries) {
let ret = this.empty();
for (let i = 0, len = entries.length; i < len; i++) {
if (i) {
ret.add(',');
}
ret.add(castChunk(entries[i], this));
}
return ret;
},
generateArray: function(entries) {
let ret = this.generateList(entries);
ret.prepend('[');
ret.add(']');
return ret;
}
};
export default CodeGen;
+333 -247
View File
@@ -1,4 +1,10 @@
import Exception from "../exception";
/* eslint-disable new-cap */
import Exception from '../exception';
import {isArray, indexOf, extend} from '../utils';
import AST from './ast';
const slice = [].slice;
export function Compiler() {}
@@ -10,54 +16,24 @@ export function Compiler() {}
Compiler.prototype = {
compiler: Compiler,
disassemble: function() {
var opcodes = this.opcodes, opcode, out = [], params, param;
for (var i=0, l=opcodes.length; i<l; i++) {
opcode = opcodes[i];
if (opcode.opcode === 'DECLARE') {
out.push("DECLARE " + opcode.name + "=" + opcode.value);
} else {
params = [];
for (var j=0; j<opcode.args.length; j++) {
param = opcode.args[j];
if (typeof param === "string") {
param = "\"" + param.replace("\n", "\\n") + "\"";
}
params.push(param);
}
out.push(opcode.opcode + " " + params.join(" "));
}
}
return out.join("\n");
},
equals: function(other) {
var len = this.opcodes.length;
let len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (var i = 0; i < len; i++) {
var opcode = this.opcodes[i],
for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
return false;
}
for (var j = 0; j < opcode.args.length; j++) {
if (opcode.args[j] !== otherOpcode.args[j]) {
return false;
}
}
}
// We know that length is the same between the two arrays because they are directly tied
// to the opcode behavior above.
len = this.children.length;
if (other.children.length !== len) {
return false;
}
for (i = 0; i < len; i++) {
for (let i = 0; i < len; i++) {
if (!this.children[i].equals(other.children[i])) {
return false;
}
@@ -69,16 +45,18 @@ Compiler.prototype = {
guid: 0,
compile: function(program, options) {
this.sourceNode = [];
this.opcodes = [];
this.children = [];
this.depths = {list: []};
this.options = options;
this.stringParams = options.stringParams;
this.trackIds = options.trackIds;
options.blockParams = options.blockParams || [];
// These changes will propagate to the other compiler components
var knownHelpers = this.options.knownHelpers;
this.options.knownHelpers = {
let knownHelpers = options.knownHelpers;
options.knownHelpers = {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
@@ -89,92 +67,83 @@ Compiler.prototype = {
'lookup': true
};
if (knownHelpers) {
for (var name in knownHelpers) {
this.options.knownHelpers[name] = knownHelpers[name];
// 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];
}
}
return this.accept(program);
},
accept: function(node) {
var strip = node.strip || {},
ret;
if (strip.left) {
this.opcode('strip');
}
ret = this[node.type](node);
if (strip.right) {
this.opcode('strip');
}
return ret;
},
program: function(program) {
var statements = program.statements;
for(var i=0, l=statements.length; i<l; i++) {
this.accept(statements[i]);
}
this.isSimple = l === 1;
this.depths.list = this.depths.list.sort(function(a, b) {
return a - b;
});
return this;
},
compileProgram: function(program) {
var result = new this.compiler().compile(program, this.options);
var guid = this.guid++, depth;
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
result = childCompiler.compile(program, this.options),
guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
for(var i=0, l=result.depths.list.length; i<l; i++) {
depth = result.depths.list[i];
if(depth < 2) { continue; }
else { this.addDepth(depth - 1); }
}
this.useDepths = this.useDepths || result.useDepths;
return guid;
},
block: function(block) {
var mustache = block.mustache,
program = block.program,
accept: function(node) {
/* istanbul ignore next: Sanity code */
if (!this[node.type]) {
throw new Exception('Unknown type: ' + node.type, node);
}
this.sourceNode.unshift(node);
let ret = this[node.type](node);
this.sourceNode.shift();
return ret;
},
Program: function(program) {
this.options.blockParams.unshift(program.blockParams);
let body = program.body,
bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]);
}
this.options.blockParams.shift();
this.isSimple = bodyLength === 1;
this.blockParams = program.blockParams ? program.blockParams.length : 0;
return this;
},
BlockStatement: function(block) {
transformLiteralToPath(block);
let program = block.program,
inverse = block.inverse;
if (program) {
program = this.compileProgram(program);
}
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
if (inverse) {
inverse = this.compileProgram(inverse);
}
let type = this.classifySexpr(block);
var sexpr = mustache.sexpr;
var type = this.classifySexpr(sexpr);
if (type === "helper") {
this.helperSexpr(sexpr, program, inverse);
} else if (type === "simple") {
this.simpleSexpr(sexpr);
if (type === 'helper') {
this.helperSexpr(block, program, inverse);
} else if (type === 'simple') {
this.simpleSexpr(block);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue', sexpr.id.original);
this.opcode('blockValue', block.path.original);
} else {
this.ambiguousSexpr(sexpr, program, inverse);
this.ambiguousSexpr(block, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
@@ -187,177 +156,219 @@ Compiler.prototype = {
this.opcode('append');
},
hash: function(hash) {
var pairs = hash.pairs, i, l;
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
this.opcode('pushHash');
for(i=0, l=pairs.length; i<l; i++) {
this.pushParam(pairs[i][1]);
}
while(i--) {
this.opcode('assignToHash', pairs[i][0]);
}
this.opcode('popHash');
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
},
partial: function(partial) {
var partialName = partial.partialName;
PartialStatement: function(partial) {
this.usePartial = true;
if (partial.hash) {
this.accept(partial.hash);
} else {
this.opcode('push', 'undefined');
let program = partial.program;
if (program) {
program = this.compileProgram(partial.program);
}
if (partial.context) {
this.accept(partial.context);
} else {
this.opcode('push', 'depth0');
let params = partial.params;
if (params.length > 1) {
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});
}
}
this.opcode('invokePartial', partialName.name);
let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
this.setupFullMustacheParams(partial, program, undefined, true);
let indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},
content: function(content) {
this.opcode('appendContent', content.string);
PartialBlockStatement: function(partialBlock) {
this.PartialStatement(partialBlock);
},
mustache: function(mustache) {
this.sexpr(mustache.sexpr);
MustacheStatement: function(mustache) {
this.SubExpression(mustache);
if(mustache.escaped && !this.options.noEscape) {
if (mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
ambiguousSexpr: function(sexpr, program, inverse) {
var id = sexpr.id,
name = id.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', id.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('invokeAmbiguous', name, isBlock);
Decorator(decorator) {
this.DecoratorBlock(decorator);
},
simpleSexpr: function(sexpr) {
var id = sexpr.id;
if (id.type === 'DATA') {
this.DATA(id);
} else if (id.parts.length) {
this.ID(id);
} else {
// Simplified ID for `this`
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('pushContext');
}
this.opcode('resolvePossibleLambda');
},
helperSexpr: function(sexpr, program, inverse) {
var params = this.setupFullMustacheParams(sexpr, program, inverse),
id = sexpr.id,
name = id.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);
} else {
this.ID(id);
this.opcode('invokeHelper', params.length, id.original, sexpr.isRoot);
ContentStatement: function(content) {
if (content.value) {
this.opcode('appendContent', content.value);
}
},
sexpr: function(sexpr) {
var type = this.classifySexpr(sexpr);
CommentStatement: function() {},
if (type === "simple") {
SubExpression: function(sexpr) {
transformLiteralToPath(sexpr);
let type = this.classifySexpr(sexpr);
if (type === 'simple') {
this.simpleSexpr(sexpr);
} else if (type === "helper") {
} else if (type === 'helper') {
this.helperSexpr(sexpr);
} else {
this.ambiguousSexpr(sexpr);
}
},
ambiguousSexpr: function(sexpr, program, inverse) {
let path = sexpr.path,
name = path.parts[0],
isBlock = program != null || inverse != null;
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('getContext', path.depth);
var name = id.parts[0];
if (!name) {
this.opcode('pushContext');
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
path.strict = true;
this.accept(path);
this.opcode('invokeAmbiguous', name, isBlock);
},
simpleSexpr: function(sexpr) {
let path = sexpr.path;
path.strict = true;
this.accept(path);
this.opcode('resolvePossibleLambda');
},
helperSexpr: function(sexpr, program, inverse) {
let params = this.setupFullMustacheParams(sexpr, program, inverse),
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);
} else {
this.opcode('lookupOnContext', id.parts[0]);
}
path.strict = true;
path.falsy = true;
for(var i=1, l=id.parts.length; i<l; i++) {
this.opcode('lookup', id.parts[i]);
this.accept(path);
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
}
},
DATA: function(data) {
this.options.data = true;
this.opcode('lookupData', data.id.depth);
var parts = data.id.parts;
for(var i=0, l=parts.length; i<l; i++) {
this.opcode('lookup', parts[i]);
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);
if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
} else if (!name) {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
} else if (path.data) {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
}
},
STRING: function(string) {
this.opcode('pushString', string.string);
StringLiteral: function(string) {
this.opcode('pushString', string.value);
},
NUMBER: function(number) {
this.opcode('pushLiteral', number.number);
NumberLiteral: function(number) {
this.opcode('pushLiteral', number.value);
},
BOOLEAN: function(bool) {
this.opcode('pushLiteral', bool.bool);
BooleanLiteral: function(bool) {
this.opcode('pushLiteral', bool.value);
},
comment: function() {},
UndefinedLiteral: function() {
this.opcode('pushLiteral', 'undefined');
},
NullLiteral: function() {
this.opcode('pushLiteral', 'null');
},
Hash: function(hash) {
let pairs = hash.pairs,
i = 0,
l = pairs.length;
this.opcode('pushHash');
for (; i < l; i++) {
this.pushParam(pairs[i].value);
}
while (i--) {
this.opcode('assignToHash', pairs[i].key);
}
this.opcode('popHash');
},
// HELPERS
opcode: function(name) {
this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
},
declare: function(name, value) {
this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
},
addDepth: function(depth) {
if(depth === 0) { return; }
if(!this.depths[depth]) {
this.depths[depth] = true;
this.depths.list.push(depth);
if (!depth) {
return;
}
this.useDepths = true;
},
classifySexpr: function(sexpr) {
var isHelper = sexpr.isHelper;
var isEligible = sexpr.eligibleHelper;
var options = this.options;
let isSimple = AST.helpers.simpleId(sexpr.path);
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
let isEligible = !isBlockParam && (isHelper || isSimple);
// if ambiguous, we can possibly resolve the ambiguity now
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
var name = sexpr.id.parts[0];
let name = sexpr.path.parts[0],
options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
@@ -366,108 +377,183 @@ Compiler.prototype = {
}
}
if (isHelper) { return "helper"; }
else if (isEligible) { return "ambiguous"; }
else { return "simple"; }
if (isHelper) {
return 'helper';
} else if (isEligible) {
return 'ambiguous';
} else {
return 'simple';
}
},
pushParams: function(params) {
for(var i=0, l=params.length; i<l; i++) {
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(val.depth) {
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', val.stringModeValue, val.type);
this.opcode('pushStringParam', value, val.type);
if (val.type === 'sexpr') {
// Subexpressions get evaluated and passed in
if (val.type === 'SubExpression') {
// SubExpressions get evaluated and passed in
// in string params mode.
this.sexpr(val);
this.accept(val);
}
} else {
if (this.trackIds) {
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
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);
}
},
setupFullMustacheParams: function(sexpr, program, inverse) {
var params = sexpr.params;
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
let params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
if (sexpr.hash) {
this.hash(sexpr.hash);
this.accept(sexpr.hash);
} else {
this.opcode('emptyHash');
this.opcode('emptyHash', omitEmpty);
}
return params;
},
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);
if (blockParams && param >= 0) {
return [depth, param];
}
}
}
};
export function precompile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
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);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
var ast = env.parse(input);
var environment = new env.Compiler().compile(ast, options);
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options);
return new env.JavaScriptCompiler().compile(environment, options);
}
export function compile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
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 = options || {};
options = extend({}, options);
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
var compiled;
let compiled;
function compileInput() {
var ast = env.parse(input);
var environment = new env.Compiler().compile(ast, options);
var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
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.
var ret = function(context, options) {
function ret(context, execOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, options);
return compiled.call(this, context, execOptions);
}
ret._setup = function(setupOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled._setup(setupOptions);
};
ret._setup = function(options) {
ret._child = function(i, data, blockParams, depths) {
if (!compiled) {
compiled = compileInput();
}
return compiled._setup(options);
};
ret._child = function(i) {
if (!compiled) {
compiled = compileInput();
}
return compiled._child(i);
return compiled._child(i, data, blockParams, depths);
};
return ret;
}
function argEquals(a, b) {
if (a === b) {
return true;
}
if (isArray(a) && isArray(b) && a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!argEquals(a[i], b[i])) {
return false;
}
}
return true;
}
}
function transformLiteralToPath(sexpr) {
if (!sexpr.path.parts) {
let literal = sexpr.path;
// Casting to string here to make false and 0 literal values play nicely with the rest
// of the system.
sexpr.path = {
type: 'PathExpression',
data: false,
depth: 0,
parts: [literal.original + ''],
original: literal.original + '',
loc: literal.loc
};
}
}
+210
View File
@@ -0,0 +1,210 @@
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
+111 -83
View File
@@ -1,4 +1,5 @@
import Visitor from "./visitor";
/* eslint-disable new-cap */
import Visitor from './visitor';
export function print(ast) {
return new PrintVisitor().accept(ast);
@@ -10,26 +11,33 @@ export function PrintVisitor() {
PrintVisitor.prototype = new Visitor();
PrintVisitor.prototype.pad = function(string, newline) {
var out = "";
PrintVisitor.prototype.pad = function(string) {
let out = '';
for(var i=0,l=this.padding; i<l; i++) {
out = out + " ";
for (let i = 0, l = this.padding; i < l; i++) {
out += ' ';
}
out = out + string;
if(newline !== false) { out = out + "\n"; }
out += string + '\n';
return out;
};
PrintVisitor.prototype.program = function(program) {
var out = "",
statements = program.statements,
PrintVisitor.prototype.Program = function(program) {
let out = '',
body = program.body,
i, l;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
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--;
@@ -37,23 +45,31 @@ PrintVisitor.prototype.program = function(program) {
return out;
};
PrintVisitor.prototype.block = function(block) {
var out = "";
PrintVisitor.prototype.MustacheStatement = function(mustache) {
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
};
out = out + this.pad("BLOCK:");
PrintVisitor.prototype.BlockStatement =
PrintVisitor.prototype.DecoratorBlock = function(block) {
let out = '';
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
this.padding++;
out = out + this.accept(block.mustache);
out += this.pad(this.SubExpression(block));
if (block.program) {
out = out + this.pad("PROGRAM:");
out += this.pad('PROGRAM:');
this.padding++;
out = out + this.accept(block.program);
out += this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
out = out + this.pad("{{^}}");
out += this.pad('{{^}}');
this.padding++;
out = out + this.accept(block.inverse);
out += this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
}
@@ -62,82 +78,94 @@ PrintVisitor.prototype.block = function(block) {
return out;
};
PrintVisitor.prototype.sexpr = function(sexpr) {
var params = sexpr.params, paramStrings = [], hash;
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);
}
for(var i=0, l=params.length; i<l; i++) {
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(", ") + "]";
params = '[' + paramStrings.join(', ') + ']';
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
return this.accept(sexpr.id) + " " + params + hash;
return this.accept(sexpr.path) + ' ' + params + hash;
};
PrintVisitor.prototype.mustache = function(mustache) {
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
PrintVisitor.prototype.PathExpression = function(id) {
let path = id.parts.join('/');
return (id.data ? '@' : '') + 'PATH:' + path;
};
PrintVisitor.prototype.partial = function(partial) {
var content = this.accept(partial.partialName);
if(partial.context) {
content += " " + this.accept(partial.context);
}
if (partial.hash) {
content += " " + this.accept(partial.hash);
}
return this.pad("{{> " + content + " }}");
PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"';
};
PrintVisitor.prototype.hash = function(hash) {
var pairs = hash.pairs;
var joinedPairs = [], left, right;
PrintVisitor.prototype.NumberLiteral = function(number) {
return 'NUMBER{' + number.value + '}';
};
for(var i=0, l=pairs.length; i<l; i++) {
left = pairs[i][0];
right = this.accept(pairs[i][1]);
joinedPairs.push( left + "=" + right );
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(", ") + "}";
return 'HASH{' + joinedPairs.join(', ') + '}';
};
PrintVisitor.prototype.STRING = function(string) {
return '"' + string.string + '"';
PrintVisitor.prototype.HashPair = function(pair) {
return pair.key + '=' + this.accept(pair.value);
};
PrintVisitor.prototype.NUMBER = function(number) {
return "NUMBER{" + number.number + "}";
};
PrintVisitor.prototype.BOOLEAN = function(bool) {
return "BOOLEAN{" + bool.bool + "}";
};
PrintVisitor.prototype.ID = function(id) {
var path = id.parts.join("/");
if(id.parts.length > 1) {
return "PATH:" + path;
} else {
return "ID:" + path;
}
};
PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
return "PARTIAL:" + partialName.name;
};
PrintVisitor.prototype.DATA = function(data) {
return "@" + this.accept(data.id);
};
PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]");
};
PrintVisitor.prototype.comment = function(comment) {
return this.pad("{{! '" + comment.comment + "' }}");
};
/* eslint-enable new-cap */
+120 -2
View File
@@ -1,11 +1,129 @@
function Visitor() {}
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) {
return this[object.type](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;
@@ -0,0 +1,216 @@
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;
+6
View File
@@ -0,0 +1,6 @@
import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
+22
View File
@@ -0,0 +1,22 @@
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;
};
}
props.partials[options.args[0]] = options.fn;
return ret;
});
}
+32 -10
View File
@@ -1,24 +1,46 @@
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
var line;
if (node && node.firstLine) {
line = node.firstLine;
let loc = node && node.loc,
line,
column;
if (loc) {
line = loc.start.line;
column = loc.start.column;
message += ' - ' + line + ':' + node.firstColumn;
message += ' - ' + line + ':' + column;
}
var tmp = Error.prototype.constructor.call(this, message);
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 (var idx = 0; idx < errorProps.length; idx++) {
for (let idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
if (line) {
this.lineNumber = line;
this.column = node.firstColumn;
/* 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 */
}
}
+17
View File
@@ -0,0 +1,17 @@
import registerBlockHelperMissing from './helpers/block-helper-missing';
import registerEach from './helpers/each';
import registerHelperMissing from './helpers/helper-missing';
import registerIf from './helpers/if';
import registerLog from './helpers/log';
import registerLookup from './helpers/lookup';
import registerWith from './helpers/with';
export function registerDefaultHelpers(instance) {
registerBlockHelperMissing(instance);
registerEach(instance);
registerHelperMissing(instance);
registerIf(instance);
registerLog(instance);
registerLookup(instance);
registerWith(instance);
}
@@ -0,0 +1,32 @@
import {appendContextPath, createFrame, isArray} from '../utils';
export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) {
let inverse = options.inverse,
fn = options.fn;
if (context === true) {
return fn(this);
} else if (context === false || context == null) {
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);
}
});
}
+79
View File
@@ -0,0 +1,79 @@
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
import Exception from '../exception';
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;
if (options.data && options.ids) {
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
function execIteration(field, index, last) {
if (data) {
data.key = field;
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])
});
}
if (context && typeof context === 'object') {
if (isArray(context)) {
for (let j = context.length; i < j; i++) {
if (i in context) {
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++;
}
}
if (priorKey !== undefined) {
execIteration(priorKey, i - 1, true);
}
}
}
if (i === 0) {
ret = inverse(this);
}
return ret;
});
}
+13
View File
@@ -0,0 +1,13 @@
import Exception from '../exception';
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 + '"');
}
});
}
+20
View File
@@ -0,0 +1,20 @@
import {isEmpty, isFunction} from '../utils';
export default function(instance) {
instance.registerHelper('if', function(conditional, options) {
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
// 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);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
}
+19
View File
@@ -0,0 +1,19 @@
export default function(instance) {
instance.registerHelper('log', function(/* message, options */) {
let args = [undefined],
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
let level = 1;
if (options.hash.level != null) {
level = options.hash.level;
} else if (options.data && options.data.level != null) {
level = options.data.level;
}
args[0] = level;
instance.log(... args);
});
}
+5
View File
@@ -0,0 +1,5 @@
export default function(instance) {
instance.registerHelper('lookup', function(obj, field) {
return obj && obj[field];
});
}
+24
View File
@@ -0,0 +1,24 @@
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
export default function(instance) {
instance.registerHelper('with', function(context, options) {
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])
});
} else {
return options.inverse(this);
}
});
}
+35
View File
@@ -0,0 +1,35 @@
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) {
if (typeof level === 'string') {
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
if (levelMap >= 0) {
level = levelMap;
} else {
level = parseInt(level, 10);
}
}
return level;
},
// Can be overridden in the host environment
log: function(level, ...message) {
level = logger.lookupLevel(level);
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
let method = logger.methodMap[level];
if (!console[method]) { // eslint-disable-line no-console
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
}
}
};
export default logger;
+13
View File
@@ -0,0 +1,13 @@
/* global window */
export default function(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;
}
return Handlebars;
};
}
+185 -74
View File
@@ -1,107 +1,160 @@
module Utils from "./utils";
import Exception from "./exception";
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base";
import * as Utils from './utils';
import Exception from './exception';
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
export function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var 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+").");
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]+").");
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] + ').');
}
}
}
// TODO: Remove this line and break up compilePartial
export function template(templateSpec, env) {
/* istanbul ignore next */
if (!env) {
throw new Exception("No environment passed to template");
throw new Exception('No environment passed to template');
}
if (!templateSpec || !templateSpec.main) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
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.
env.VM.checkRevision(templateSpec.compiler);
var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
if (hash) {
context = Utils.extend({}, context, hash);
function invokePartialWrapper(partial, context, options) {
if (options.hash) {
context = Utils.extend({}, context, options.hash);
if (options.ids) {
options.ids[0] = true;
}
}
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
if (result != null) { return result; }
partial = env.VM.resolvePartial.call(this, partial, context, options);
let result = env.VM.invokePartial.call(this, partial, context, options);
if (env.compile) {
var options = { helpers: helpers, partials: partials, data: data };
partials[name] = env.compile(partial, { data: data !== undefined }, env);
return partials[name](context, options);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
result = options.partials[options.name](context, options);
}
if (result != null) {
if (options.indent) {
let lines = result.split('\n');
for (let i = 0, l = lines.length; i < l; i++) {
if (!lines[i] && i + 1 === l) {
break;
}
lines[i] = options.indent + lines[i];
}
result = lines.join('\n');
}
return result;
} else {
throw new Exception("The partial " + 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
var container = {
let container = {
strict: function(obj, name) {
if (!(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj);
}
return obj[name];
},
lookup: function(depths, name) {
const len = depths.length;
for (let i = 0; i < len; i++) {
if (depths[i] && depths[i][name] != null) {
return depths[i][name];
}
}
},
lambda: function(current, context) {
return typeof current === 'function' ? current.call(context) : current;
},
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
fn: function(i) {
return templateSpec[i];
let ret = templateSpec[i];
ret.decorator = templateSpec[i + '_d'];
return ret;
},
programs: [],
program: function(i, data) {
var programWrapper = this.programs[i],
program: function(i, data, declaredBlockParams, blockParams, depths) {
let programWrapper = this.programs[i],
fn = this.fn(i);
if(data) {
programWrapper = program(this, i, fn, data);
if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(this, i, fn);
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
}
return programWrapper;
},
programWithDepth: env.VM.programWithDepth,
data: function(data, depth) {
while (data && depth--) {
data = data._parent;
data: function(value, depth) {
while (value && depth--) {
value = value._parent;
}
return data;
return value;
},
merge: function(param, common) {
var ret = param || common;
let obj = param || common;
if (param && common && (param !== common)) {
ret = Utils.extend({}, common, param);
obj = Utils.extend({}, common, param);
}
return ret;
return obj;
},
// An empty object to use as replacement for null-contexts
nullContext: Object.seal({}),
noop: env.VM.noop,
compilerInfo: templateSpec.compiler
};
var ret = function(context, options) {
options = options || {};
var helpers,
partials,
data = options.data;
function ret(context, options = {}) {
let data = options.data;
ret._setup(options);
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
return templateSpec.main.call(container, context, container.helpers, container.partials, data);
};
let depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
if (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);
}
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
return main(context, options);
}
ret.isTop = true;
ret._setup = function(options) {
if (!options.partial) {
@@ -110,56 +163,105 @@ export function template(templateSpec, env) {
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = container.merge(options.decorators, env.decorators);
}
} else {
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
}
};
ret._child = function(i) {
return container.programWithDepth(i);
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 programWithDepth(i, data /*, $depth */) {
/*jshint -W040 */
var args = Array.prototype.slice.call(arguments, 2),
container = this,
fn = container.fn(i);
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)) {
currentDepths = [context].concat(depths);
}
var prog = function(context, options) {
options = options || {};
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);
return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
};
prog.program = i;
prog.depth = args.length;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
return prog;
}
export function program(container, i, fn, data) {
var prog = function(context, options) {
options = options || {};
return fn.call(container, context, container.helpers, container.partials, options.data || data);
};
prog.program = i;
prog.depth = 0;
return prog;
export function resolvePartial(partial, context, options) {
if (!partial) {
if (options.name === '@partial-block') {
partial = options.data['partial-block'];
} else {
partial = options.partials[options.name];
}
} else if (!partial.call && !options.name) {
// This is a dynamic partial that returned a string
options.name = partial;
partial = options.partials[partial];
}
return partial;
}
export function invokePartial(partial, name, context, helpers, partials, data) {
var options = { partial: true, helpers: helpers, partials: partials, data: data };
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;
}
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
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 = {}) {
// 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);
options.data['partial-block'] = currentPartialBlock;
return fn(context, options);
};
if (fn.partials) {
options.partials = Utils.extend({}, options.partials, fn.partials);
}
}
if (partial === undefined && partialBlock) {
partial = partialBlock;
}
if (partial === undefined) {
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)) {
@@ -168,3 +270,12 @@ function initData(context, data) {
}
return 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);
Utils.extend(prog, props);
}
return prog;
}
+2 -2
View File
@@ -3,8 +3,8 @@ function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = function() {
return "" + this.string;
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
return '' + this.string;
};
export default SafeString;
+58 -31
View File
@@ -1,25 +1,23 @@
/*jshint -W004 */
import SafeString from "./safe-string";
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
const escape = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;',
'=': '&#x3D;'
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
const badChars = /[&<>"'`=]/g,
possible = /[&<>"'`=]/;
function escapeChar(chr) {
return escape[chr] || "&amp;";
return escape[chr];
}
export function extend(obj /* , ...source */) {
for (var i = 1; i < arguments.length; i++) {
for (var key in arguments[i]) {
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)) {
obj[key] = arguments[i][key];
}
@@ -29,40 +27,58 @@ export function extend(obj /* , ...source */) {
return obj;
}
export var toString = Object.prototype.toString;
export let toString = Object.prototype.toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
var isFunction = function(value) {
/* eslint-disable func-style */
let isFunction = function(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 var isFunction;
export {isFunction};
/* eslint-enable func-style */
export var isArray = Array.isArray || function(value) {
/* istanbul ignore next */
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) {
for (let i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
return -1;
}
export function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (!string && string !== 0) {
return "";
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
if (string && string.toHTML) {
return string.toHTML();
} else if (string == null) {
return '';
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = '' + string;
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = "" + string;
if(!possible.test(string)) { return string; }
if (!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
}
@@ -76,6 +92,17 @@ export function isEmpty(value) {
}
}
export function createFrame(object) {
let frame = extend({}, 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;
}
+11 -11
View File
@@ -1,11 +1,10 @@
// USAGE:
// var handlebars = require('handlebars');
/* eslint-disable no-var */
// var local = handlebars.create();
var handlebars = require('../dist/cjs/handlebars')["default"];
handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor')["default"];
var handlebars = require('../dist/cjs/handlebars')['default'];
var printer = require('../dist/cjs/handlebars/compiler/printer');
handlebars.PrintVisitor = printer.PrintVisitor;
@@ -14,12 +13,13 @@ handlebars.print = printer.print;
module.exports = handlebars;
// Publish a Node.js require() handler for .handlebars and .hbs files
if (typeof require !== 'undefined' && require.extensions) {
var extension = function(module, filename) {
var fs = require("fs");
var templateString = fs.readFileSync(filename, "utf8");
module.exports = handlebars.compile(templateString);
};
require.extensions[".handlebars"] = extension;
require.extensions[".hbs"] = extension;
function extension(module, filename) {
var fs = require('fs');
var templateString = fs.readFileSync(filename, 'utf8');
module.exports = handlebars.compile(templateString);
}
/* istanbul ignore else */
if (typeof require !== 'undefined' && require.extensions) {
require.extensions['.handlebars'] = extension;
require.extensions['.hbs'] = extension;
}
+298
View File
@@ -0,0 +1,298 @@
/* 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';
module.exports.loadTemplates = function(opts, callback) {
loadStrings(opts, function(err, strings) {
if (err) {
callback(err);
} else {
loadFiles(opts, function(err, files) {
if (err) {
callback(err);
} else {
opts.templates = strings.concat(files);
callback(undefined, opts);
}
});
}
});
};
function loadStrings(opts, callback) {
let strings = arrayCast(opts.string),
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'));
}
Async.map(strings, function(string, callback) {
if (string !== '-') {
callback(undefined, string);
} else {
// Load from stdin
let buffer = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
buffer += chunk;
});
process.stdin.on('end', function() {
callback(undefined, buffer);
});
}
},
function(err, strings) {
strings = strings.map((string, index) => ({
name: names[index],
path: names[index],
source: string
}));
callback(err, strings);
});
}
function loadFiles(opts, callback) {
// Build file extension pattern
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();
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;
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});
}
});
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 {
callback(undefined, ret);
}
});
}
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.');
}
if (opts.simple && opts.min) {
throw new Handlebars.Exception('Unable to minimize simple output');
}
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) {
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) {
opts.simple = true;
}
// Convert the known list into a hash
let known = {};
if (opts.known && !Array.isArray(opts.known)) {
opts.known = [opts.known];
}
if (opts.known) {
for (let i = 0, len = opts.known.length; i < len; i++) {
known[opts.known[i]] = true;
}
}
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
let output = new SourceNode();
if (!opts.simple) {
if (opts.amd) {
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 {
output.add('(function() {\n');
}
output.add(' var template = Handlebars.template, templates = ');
if (opts.namespace) {
output.add(opts.namespace);
output.add(' = ');
output.add(opts.namespace);
output.add(' || ');
}
output.add('{};\n');
}
opts.templates.forEach(function(template) {
let options = {
knownHelpers: known,
knownHelpersOnly: opts.o
};
if (opts.map) {
options.srcName = template.path;
}
if (opts.data) {
options.data = true;
}
let precompiled = Handlebars.precompile(template.source, options);
// 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);
}
if (opts.simple) {
output.add([precompiled, '\n']);
} else {
if (!template.name) {
throw new Handlebars.Exception('Name missing for template');
}
if (opts.amd && !multiple) {
output.add('return ');
}
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
}
});
// Output the content
if (!opts.simple) {
if (opts.amd) {
if (multiple) {
output.add(['return ', objectName, ';\n']);
}
output.add('});');
} else if (!opts.commonjs) {
output.add('})();');
}
}
if (opts.map) {
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
}
output = output.toStringWithSourceMap();
output.map = output.map + '';
if (opts.min) {
output = minify(output, opts.map);
}
if (opts.map) {
fs.writeFileSync(opts.map, output.map, 'utf8');
}
output = output.code;
if (opts.output) {
fs.writeFileSync(opts.output, output, 'utf8');
} else {
console.log(output);
}
};
function arrayCast(value) {
value = value != null ? value : [];
if (!Array.isArray(value)) {
value = [value];
}
return value;
}
/**
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
*
* We are using `require` instead of `import` here, because es6-modules do not allow
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
* should not be a problem.
*
* @param {string} output the compiled template
* @param {string} sourceMapFile the file to write the source map to.
*/
function minify(output, sourceMapFile) {
try {
// Try to resolve uglify-js in order to see if it does exist
require.resolve('uglify-js');
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
// Something else seems to be wrong
throw e;
}
// it does not exist!
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
}
});
}
+114
View File
@@ -0,0 +1,114 @@
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
@@ -0,0 +1 @@
Author: Yehuda
+13
View File
@@ -0,0 +1,13 @@
// 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
@@ -0,0 +1,21 @@
#!/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
@@ -0,0 +1 @@
Author: {{author}}
+51 -24
View File
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"barename": "handlebars",
"version": "2.0.0-alpha.4",
"version": "4.1.1",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
@@ -21,41 +21,68 @@
"node": ">=0.4.7"
},
"dependencies": {
"optimist": "~0.3"
"neo-async": "^2.6.0",
"optimist": "^0.6.1",
"source-map": "^0.6.1"
},
"optionalDependencies": {
"uglify-js": "~2.3"
"uglify-js": "^3.1.4"
},
"devDependencies": {
"async": "~0.2.9",
"aws-sdk": "~1.5.0",
"aws-sdk": "^2.1.49",
"babel-loader": "^5.0.0",
"babel-runtime": "^5.1.10",
"benchmark": "~1.0",
"dustjs-linkedin": "~2.0.2",
"dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.10",
"grunt-contrib-clean": "~0.4.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "0.x",
"grunt-contrib-requirejs": "~0.4.1",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-saucelabs": "~5.0.1",
"es6-module-packager": "1.x",
"grunt": "^1.0.3",
"grunt-babel": "^5.0.0",
"grunt-cli": "^1",
"grunt-contrib-clean": "^1",
"grunt-contrib-concat": "^1",
"grunt-contrib-connect": "^1",
"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-webpack": "^1.0.8",
"istanbul": "^0.3.0",
"jison": "~0.3.0",
"keen.io": "0.0.3",
"mocha": "*",
"mustache": "~0.7.2",
"semver": "~2.1.0",
"underscore": "~1.5.1"
"mocha": "^5",
"mock-stdin": "^0.3.0",
"mustache": "^2.1.3",
"semver": "^5.0.1",
"underscore": "^1.5.1",
"webpack": "^1.12.6",
"webpack-dev-server": "^1.12.1"
},
"main": "lib/index.js",
"types": "lib/handlebars.d.ts",
"bin": {
"handlebars": "bin/handlebars"
},
"scripts": {
"test": "grunt"
}
},
"jspm": {
"main": "handlebars",
"directories": {
"lib": "dist/amd"
},
"buildConfig": {
"minify": true
}
},
"files": [
"bin",
"dist/*.js",
"dist/amd/**/*.js",
"dist/cjs/**/*.js",
"lib",
"print-script",
"release-notes.md",
"runtime.js"
]
}
Executable
+95
View File
@@ -0,0 +1,95 @@
#! /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);
}
});
}
+492 -56
View File
@@ -2,7 +2,443 @@
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...master)
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.1...master)
## v4.1.1 - March 16th, 2019
Bugfixes:
- fix: add "runtime.d.ts" to allow "require('handlebars/runtime')" in TypeScript - 5cedd62
Refactorings:
- replace "async" with "neo-async" - 048f2ce
- use "substring"-function instead of "substr" - 445ae12
Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.1...v4.1.1)
## v4.1.1 - March 16th, 2019
Bugfixes:
- fix: add "runtime.d.ts" to allow "require('handlebars/runtime')" in TypeScript - 5cedd62
Refactorings:
- replace "async" with "neo-async" - 048f2ce
- use "substring"-function instead of "substr" - 445ae12
Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.0...v4.1.1)
## v4.1.0 - February 7th, 2019
New Features
- import TypeScript typings - 27ac1ee
Security fixes:
- disallow access to the constructor in templates to prevent RCE - 42841c4, #1495
Housekeeping
- chore: fix components/handlebars package.json and auto-update on release - bacd473
- chore: Use node 10 to build handlebars - 78dd89c
- chore/doc: Add more release docs - 6b87c21
Compatibility notes:
Access to class constructors (i.e. `({}).constructor`) is now prohibited to prevent
Remote Code Execution. This means that following construct will no work anymore:
```
class SomeClass {
}
SomeClass.staticProperty = 'static'
var template = Handlebars.compile('{{constructor.staticProperty}}');
document.getElementById('output').innerHTML = template(new SomeClass());
// expected: 'static', but now this is empty.
```
This kind of access is not the intended use of Handlebars and leads to the vulnerability described in #1495. We will **not** increase the major version, because such use is not intended or documented, and because of the potential impact of the issue (we fear that most people won't use a new major version and the issue may not be resolved on many systems).
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.1.0)
## v4.0.12 - September 4th, 2018
New features:
- none
Various dependency updates
- [#1464](https://github.com/wycats/handlebars.js/pull/1464) - Bump versions of grunt-plugins to 1.x
- [#1398](https://github.com/wycats/handlebars.js/pull/1398) - Chore: updated various dev dependencies
- upgrade uglify-js - d3d3942
- Update grunt-eslint to 20.1.0 - 7729aa9
- Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 (73d5637)
Bugfixes:
- [components/handlebars.js#24](https://github.com/components/handlebars.js#24) Add package.json to components shim
- Updated `source-map`-package should work better with `rollup`[#1463](https://github.com/wycats/handlebars.js/issues/1463)
Removed obsolete code:
- unnecessary check - 0ddff8b
- Use `files` field - 69c6ca5
- Update jsfiddle to 4.0.11 - 8947dd0
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.0.12)
## v4.0.12 - September 4th, 2018
New features:
- none
Various dependency updates
- [#1464](https://github.com/wycats/handlebars.js/pull/1464) - Bump versions of grunt-plugins to 1.x
- [#1398](https://github.com/wycats/handlebars.js/pull/1398) - Chore: updated various dev dependencies
- upgrade uglify-js - d3d3942
- Update grunt-eslint to 20.1.0 - 7729aa9
- Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 (73d5637)
Bugfixes:
- [components/handlebars.js#24](https://github.com/components/handlebars.js#24) Add package.json to components shim
- Updated `source-map`-package should work better with `rollup`[#1463](https://github.com/wycats/handlebars.js/issues/1463)
Removed obsolete code:
- unnecessary check - 0ddff8b
- Use `files` field - 69c6ca5
- Update jsfiddle to 4.0.11 - 8947dd0
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.0.12)
## v4.0.12 - September 4th, 2018
New features:
- none
Various dependency updates
- [#1464](https://github.com/wycats/handlebars.js/pull/1464) - Bump versions of grunt-plugins to 1.x
- [#1398](https://github.com/wycats/handlebars.js/pull/1398) - Chore: updated various dev dependencies
- upgrade uglify-js - d3d3942
- Update grunt-eslint to 20.1.0 - 7729aa9
- Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 (73d5637)
Bugfixes:
- [components/handlebars.js#24](https://github.com/components/handlebars.js#24) Add package.json to components shim
- Updated `source-map`-package should work better with `rollup`[#1463](https://github.com/wycats/handlebars.js/issues/1463)
Removed obsolete code:
- unnecessary check - 0ddff8b
- Use `files` field - 69c6ca5
- Update jsfiddle to 4.0.11 - 8947dd0
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...v4.0.12)
## v4.0.11 - October 17th, 2017
- [#1391](https://github.com/wycats/handlebars.js/issues/1391) - `uglify-js` is unconditionally imported, but only listed as optional dependency ([@Turbo87](https://github.com/Turbo87))
- [#1233](https://github.com/wycats/handlebars.js/issues/1233) - Unable to build under windows - error at test:bin task ([@blikblum](https://github.com/blikblum))
- Update (C) year in the LICENSE file - 21386b6
Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.0.11)
## v4.0.10 - May 21st, 2017
- Fix regression in 4.0.9: Replace "Object.assign" (not support in IE) by "util/extend" - 0e953d1
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.9...v4.0.10)
## v4.0.9 - May 21st, 2017
- [#1327](https://github.com/wycats/handlebars.js/issues/1327) Handlebars.compile() does not modify "options" anymore
- pending [#1331](https://github.com/wycats/handlebars.js/issues/1331) Attempts to build Handlebars in a Windows environment
- Fix build in windows - cc554a5
- Ensure LF line-edings in handlebars-template fixtures (*.hbs) - ed879a6
- Run integration test with `node handlebars -a ...` on Windows - 2e21e2b
- Ensure LF line-edings in lexer-files (*.l) - bdfdbea
- Force LF line-endings for spec/artifacts - b50ef03
- Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul - 6e6269f
- TravisCI: Publish valid semver tags independently of the branch - 7378f85
Compatibility notes:
- No compatibility issues are expected.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.8...v4.0.9)
## v4.0.8 - May 2nd, 2017
- [#1341](https://github.com/wycats/handlebars.js/issues/1341) [#1342](https://github.com/wycats/handlebars.js/issues/1342) Allow partial-blocks to be executed without "options" ([@nknapp](https://github.com/nknapp)) - a00c598
Compatibility notes:
- No breaking changes
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.7...v4.0.8)
## v4.0.7 - April 29th, 2017
- [#1319](https://github.com/wycats/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57
- [#1315](https://github.com/wycats/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375
- [#1290](https://github.com/wycats/handlebars.js/pull/1290) [#1252](https://github.com/wycats/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c
- [#1252](https://github.com/wycats/handlebars.js/issue/1290) Using @partial-block twice in a template not possible ([@nknapp](https://github.com/nknapp)) - 5a164d0
- [#1310](https://github.com/wycats/handlebars.js/pull/1310) Avoid duplicate "sourceMappingURL=" lines. ([@joonas-lahtinen](https://github.com/joonas-lahtinen)) - 01b0f65
- [#1275](https://github.com/wycats/handlebars.js/pull/1275) require('sys') is deprecated, using 'util' instead ([@travnels](https://github.com/travnels)) - 406f2ee
- [#1285](https://github.com/wycats/handlebars.js/pull/1285) [#1284](https://github.com/wycats/handlebars.js/issues/1284) Make "column"-property of Errors enumerable ([@nknapp](https://github.com/nknapp)) - a023cb4
- [#1285](https://github.com/wycats/handlebars.js/pull/1285) Testcase to verify that compile-errors have a column-property ([@nknapp](https://github.com/nknapp)) - c7dc353
[Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...v4.0.7)
## v4.0.6 - November 12th, 2016
- [#1243](https://github.com/wycats/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea))
- [#1210](https://github.com/wycats/handlebars.js/pull/1210) - Add a new lightweight package based on handlebars in the README ([@kabirbaidhya](https://github.com/kabirbaidhya))
- [#1187](https://github.com/wycats/handlebars.js/pull/1187) - Ensure that existing blockParams and depths are respected on dupe programs ([@charleso](https://github.com/charleso))
- [#1191](https://github.com/wycats/handlebars.js/pull/1191) - Added cory ([@leo](https://github.com/leo))
- [#1177](https://github.com/wycats/handlebars.js/pull/1177) - Preserve License info in Closure Compiler ([@gennadiylitvinyuk](https://github.com/gennadiylitvinyuk))
- [#1171](https://github.com/wycats/handlebars.js/pull/1171) - Contributing doc fix: failing thats -> failing tests ([@paulfalgout](https://github.com/paulfalgout))
- [#1166](https://github.com/wycats/handlebars.js/pull/1166) - Update license date ([@timwangdev](https://github.com/timwangdev))
- Update jsfiddle to point to latest - 959ee55 (originally dfc7554 by [@kpdecker](https://github.com/kpdecker))
- [#1163](https://github.com/wycats/handlebars.js/pull/1163) - Fix typos on decorators-api.md. ([@adjohnson916](https://github.com/adjohnson916))
- Drop extra Error params - 8c19874 (originally 63fdb92 by [@kpdecker](https://github.com/kpdecker))
- [#1153](https://github.com/wycats/handlebars.js/pull/1153) - Add documentation for running tests to contributing.md ([@ryanmurakami](https://github.com/ryanmurakami))
- Avoid error in older browsers in test - 400916c (originally a6121ca by [@kpdecker](https://github.com/kpdecker))
- Update target browser test versions - fee2334 (originally 871c32a by [@kpdecker](https://github.com/kpdecker))
- Exclude coverage check in exception conditional - 32d6363 (originally 326734b by [@kpdecker](https://github.com/kpdecker))
- Fix throw when creating exception object in Safari - 20c965c (originally 2ea6119 by [@kpdecker](https://github.com/kpdecker))
- Update build for modern node versions - 6c9f98c (originally 8289c0b by [@kpdecker](https://github.com/kpdecker))
- [#1135](https://github.com/wycats/handlebars.js/issues/1135) - Relax depth check for context push - c393c81 (originally 25458fd by [@kpdecker](https://github.com/kpdecker))
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.5...v4.0.6)
## v4.0.5 - November 19th, 2015
- [#1132](https://github.com/wycats/handlebars.js/pull/1132) - Update uglify-js to avoid vulnerability ([@plynchnlm](https://github.com/plynchnlm))
- [#1129](https://github.com/wycats/handlebars.js/issues/1129) - Minified lib returns an empty string ([@bricss](https://github.com/bricss))
- Return current handlebars instance from noConflict - 685cf92
- Add webpack to dev dependency to support npm 3 - 7a6c228
- Further relax uglify dependency - 0a3b3c2
- Include tests for minimized artifacts - c21118d
- Fix lint errors under latest eslint - 9f59de9
- Add print-script helper script - 98a6717
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.4...v4.0.5)
## v4.0.4 - October 29th, 2015
- [#1121](https://github.com/wycats/handlebars.js/pull/1121) - Include partial name in 'undefined partial' exception message ([@shinypb](https://github.com/shinypb))
- [#1125](https://github.com/wycats/handlebars.js/pull/1125) - Add promised-handlebars to "in-the-wild"-list ([@nknapp](https://github.com/nknapp))
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.3...v4.0.4)
## v4.0.3 - September 23rd, 2015
- [#1099](https://github.com/wycats/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://github.com/btmorex))
- [#1093](https://github.com/wycats/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://github.com/florianpilz))
- [#1092](https://github.com/wycats/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://github.com/distantnative))
- [#1091](https://github.com/wycats/handlebars.js/pull/1091) - fix typo in release notes ([@nikolas](https://github.com/nikolas))
- [#1090](https://github.com/wycats/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://github.com/nikolas))
Compatibility notes:
- `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode.
- `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...v4.0.3)
## v4.0.2 - September 4th, 2015
- [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://github.com/michaellopez))
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.1...v4.0.2)
## v4.0.1 - September 2nd, 2015
- Fix failure when using decorators in partials - 05b82a2
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.0...v4.0.1)
## v4.0.0 - September 1st, 2015
- [#1082](https://github.com/wycats/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://github.com/kpdecker))
- [#1076](https://github.com/wycats/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://github.com/kpdecker))
- [#1087](https://github.com/wycats/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://github.com/denniskuczynski))
- [#1084](https://github.com/wycats/handlebars.js/pull/1084) - Bump uglify version to fix vulnerability ([@John-Steidley](https://github.com/John-Steidley))
- [#1068](https://github.com/wycats/handlebars.js/pull/1068) - Fix typo ([@0xack13](https://github.com/0xack13))
- [#1060](https://github.com/wycats/handlebars.js/pull/1060) - #1056 Fixed grammar for nested raw blocks ([@ericbn](https://github.com/ericbn))
- [#1052](https://github.com/wycats/handlebars.js/pull/1052) - Updated year in License ([@maqnouch](https://github.com/maqnouch))
- [#1037](https://github.com/wycats/handlebars.js/pull/1037) - Fix minor typos in README ([@tomxtobin](https://github.com/tomxtobin))
- [#1032](https://github.com/wycats/handlebars.js/issues/1032) - Is it possible to render a partial without the parent scope? ([@aputinski](https://github.com/aputinski))
- [#1019](https://github.com/wycats/handlebars.js/pull/1019) - Fixes typo in tests ([@aymerick](https://github.com/aymerick))
- [#1016](https://github.com/wycats/handlebars.js/issues/1016) - Version mis-match ([@mayankdedhia](https://github.com/mayankdedhia))
- [#1023](https://github.com/wycats/handlebars.js/issues/1023) - is it possible for nested custom helpers to communicate between each other?
- [#893](https://github.com/wycats/handlebars.js/issues/893) - [Proposal] Section blocks.
- [#792](https://github.com/wycats/handlebars.js/issues/792) - feature request: inline partial definitions
- [#583](https://github.com/wycats/handlebars.js/issues/583) - Parent path continues to drill down depth with multiple conditionals
- [#404](https://github.com/wycats/handlebars.js/issues/404) - Add named child helpers that can be referenced by block helpers
- Escape = in HTML content - [83b8e84](https://github.com/wycats/handlebars.js/commit/83b8e84)
- Drop AST constructors in favor of JSON - [95d84ba](https://github.com/wycats/handlebars.js/commit/95d84ba)
- Pass container rather than exec as context - [9a2d1d6](https://github.com/wycats/handlebars.js/commit/9a2d1d6)
- Add ignoreStandalone compiler option - [ea3a5a1](https://github.com/wycats/handlebars.js/commit/ea3a5a1)
- Ignore empty when iterating on sparse arrays - [06d515a](https://github.com/wycats/handlebars.js/commit/06d515a)
- Add support for string and stdin precompilation - [0de8dac](https://github.com/wycats/handlebars.js/commit/0de8dac)
- Simplify object assignment generation logic - [77e6bfc](https://github.com/wycats/handlebars.js/commit/77e6bfc)
- Bulletproof AST.helpers.helperExpression - [93b0760](https://github.com/wycats/handlebars.js/commit/93b0760)
- Always return string responses - [8e868ab](https://github.com/wycats/handlebars.js/commit/8e868ab)
- Pass undefined fields to helpers in strict mode - [5d4b8da](https://github.com/wycats/handlebars.js/commit/5d4b8da)
- Avoid depth creation when context remains the same - [279e038](https://github.com/wycats/handlebars.js/commit/279e038)
- Improve logging API - [9a49d35](https://github.com/wycats/handlebars.js/commit/9a49d35)
- Fix with operator in no @data mode - [231a8d7](https://github.com/wycats/handlebars.js/commit/231a8d7)
- Allow empty key name in each iteration - [1bb640b](https://github.com/wycats/handlebars.js/commit/1bb640b)
- Add with block parameter support - [2a85106](https://github.com/wycats/handlebars.js/commit/2a85106)
- Fix escaping of non-javascript identifiers - [410141c](https://github.com/wycats/handlebars.js/commit/410141c)
- Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa)
Compatibility notes:
- Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028).
- The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces.
- AST constructors have been dropped in favor of plain old javascript objects
- The runtime version has been increased. Precompiled templates will need to use runtime of at least 4.0.0.
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...v4.0.0)
## v3.0.3 - April 28th, 2015
- [#1004](https://github.com/wycats/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://github.com/boskee))
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.2...v3.0.3)
## v3.0.2 - April 20th, 2015
- [#998](https://github.com/wycats/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://github.com/kpdecker))
- [#994](https://github.com/wycats/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://github.com/tamlyn))
- [#990](https://github.com/wycats/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://github.com/blimmer))
- [#989](https://github.com/wycats/handlebars.js/issues/989) - Source-map error with requirejs ([@SteppeEagle](https://github.com/SteppeEagle))
- [#967](https://github.com/wycats/handlebars.js/issues/967) - can't access "this" property ([@75lb](https://github.com/75lb))
- Use captureStackTrace for error handler - a009a97
- Ignore branches tested without coverage monitoring - 37a664b
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.1...v3.0.2)
## v3.0.1 - March 24th, 2015
- [#984](https://github.com/wycats/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://github.com/johneke))
- [#973](https://github.com/wycats/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://github.com/elover))
- [#966](https://github.com/wycats/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://github.com/abloomston))
- [#964](https://github.com/wycats/handlebars.js/pull/964) - default is a reserved word ([@grassick](https://github.com/grassick))
- [#962](https://github.com/wycats/handlebars.js/pull/962) - Add dashbars' link on README. ([@pismute](https://github.com/pismute))
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.0...v3.0.1)
## v3.0.0 - February 10th, 2015
- [#941](https://github.com/wycats/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://github.com/kpdecker))
- [#940](https://github.com/wycats/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://github.com/mattflaschen))
- [#938](https://github.com/wycats/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://github.com/diwo))
- [#930](https://github.com/wycats/handlebars.js/pull/930) - Add parent tracking and mutation to AST visitors ([@kpdecker](https://github.com/kpdecker))
- [#926](https://github.com/wycats/handlebars.js/issues/926) - Depthed lookups fail when program duplicator runs ([@kpdecker](https://github.com/kpdecker))
- [#918](https://github.com/wycats/handlebars.js/pull/918) - Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos ([@oneeman](https://github.com/oneeman))
- [#915](https://github.com/wycats/handlebars.js/pull/915) - Ast update ([@kpdecker](https://github.com/kpdecker))
- [#910](https://github.com/wycats/handlebars.js/issues/910) - Different behavior of {{@last}} when {{#each}} in {{#each}} ([@zordius](https://github.com/zordius))
- [#907](https://github.com/wycats/handlebars.js/issues/907) - Implement named helper variable references ([@kpdecker](https://github.com/kpdecker))
- [#906](https://github.com/wycats/handlebars.js/pull/906) - Add parser support for block params ([@mmun](https://github.com/mmun))
- [#903](https://github.com/wycats/handlebars.js/issues/903) - Only provide aliases for multiple use calls ([@kpdecker](https://github.com/kpdecker))
- [#902](https://github.com/wycats/handlebars.js/pull/902) - Generate Source Maps ([@kpdecker](https://github.com/kpdecker))
- [#901](https://github.com/wycats/handlebars.js/issues/901) - Still escapes with noEscape enabled on isolated Handlebars environment ([@zedknight](https://github.com/zedknight))
- [#896](https://github.com/wycats/handlebars.js/pull/896) - Simplify BlockNode by removing intermediate MustacheNode ([@mmun](https://github.com/mmun))
- [#892](https://github.com/wycats/handlebars.js/pull/892) - Implement parser for else chaining of helpers ([@kpdecker](https://github.com/kpdecker))
- [#889](https://github.com/wycats/handlebars.js/issues/889) - Consider extensible parser API ([@kpdecker](https://github.com/kpdecker))
- [#887](https://github.com/wycats/handlebars.js/issues/887) - Handlebars.noConflict() option? ([@bradvogel](https://github.com/bradvogel))
- [#886](https://github.com/wycats/handlebars.js/issues/886) - Add SafeString to context (or use duck-typing) ([@dominicbarnes](https://github.com/dominicbarnes))
- [#870](https://github.com/wycats/handlebars.js/pull/870) - Registering undefined partial throws exception. ([@max-b](https://github.com/max-b))
- [#866](https://github.com/wycats/handlebars.js/issues/866) - comments don't respect whitespace control ([@75lb](https://github.com/75lb))
- [#863](https://github.com/wycats/handlebars.js/pull/863) - + jsDelivr CDN info ([@tomByrer](https://github.com/tomByrer))
- [#858](https://github.com/wycats/handlebars.js/issues/858) - Disable new default auto-indent at included partials ([@majodev](https://github.com/majodev))
- [#856](https://github.com/wycats/handlebars.js/pull/856) - jspm compatibility ([@MajorBreakfast](https://github.com/MajorBreakfast))
- [#805](https://github.com/wycats/handlebars.js/issues/805) - Request: "strict" lookups ([@nzakas](https://github.com/nzakas))
- Export the default object for handlebars/runtime - 5594416
- Lookup partials when undefined - 617dd57
Compatibility notes:
- Runtime breaking changes. Must match 3.x runtime and precompiler.
- The AST has been upgraded to a public API.
- There are a number of changes to this, but the format is now documented in docs/compiler-api.md
- The Visitor API has been expanded to support mutation and provide a base implementation
- The `JavaScriptCompiler` APIs have been formalized and documented. As part of the sourcemap handling these should be updated to return arrays for concatenation.
- `JavaScriptCompiler.namespace` has been removed as it was unused.
- `SafeString` is now duck typed on `toHTML`
New Features:
- noConflict
- Source Maps
- Block Params
- Strict Mode
- @last and other each changes
- Chained else blocks
- @data methods can now have helper parameters passed to them
- Dynamic partials
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...v3.0.0)
## v2.0.0 - September 1st, 2014
- Update jsfiddle to 2.0.0-beta.1 - 0670f65
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c
- Play nice with gemspec version numbers - 64d5481
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
## v2.0.0-beta.1 - August 26th, 2014
- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://github.com/kpdecker))
- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://github.com/scoot557))
- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://github.com/evensoul))
- Changes to 0/undefined handling
- [#731](https://github.com/wycats/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://github.com/kpdecker))
- [#820](https://github.com/wycats/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://github.com/zordius))
- [#837](https://github.com/wycats/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://github.com/zordius))
- [#819](https://github.com/wycats/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://github.com/kpdecker))
- [#764](https://github.com/wycats/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://github.com/kpdecker))
- [#773](https://github.com/wycats/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention ([@Bertrand](https://github.com/Bertrand))
- [#783](https://github.com/wycats/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://github.com/ErisDS))
- [#795](https://github.com/wycats/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://github.com/jwietelmann))
- [#823](https://github.com/wycats/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://github.com/dan-manges))
- [#834](https://github.com/wycats/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://github.com/mmun))
- [#852](https://github.com/wycats/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://github.com/zordius))
- [#835](https://github.com/wycats/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
- Expose escapeExpression on the root object - 980c38c
- Remove nested function eval in blockHelperMissing - 6f22ec1
- Fix compiler program de-duping - 9e3f824
Compatibility notes:
- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
- `false` values are now printed to output rather than silently dropped
- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
- Partials that are standalone will now indent their rendered content
- `AST.ProgramNode`'s signature has changed.
- Numerious methods/features removed from psuedo-API classes
- `JavaScriptCompiler.register`
- `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
- `Compiler.disassemble`
- `DECLARE` opcode
- `strip` opcode
- `lookup` opcode
- Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
- Removed unused `Handlebars.registerHelper` `inverse` parameter
- `each` helper requires iterator parameter
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
## v2.0.0-alpha.4 - May 19th, 2014
- Expose setup wrappers for compiled templates - 3638874
@@ -10,18 +446,18 @@
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4)
## v2.0.0-alpha.3 - May 19th, 2014
- [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://api.github.com/users/tomdale))
- [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://api.github.com/users/mmun))
- [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://github.com/tomdale))
- [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://github.com/mmun))
- Refactor template init logic - 085e5e1
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3)
## v2.0.0-alpha.2 - March 6th, 2014
- [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://api.github.com/users/jenseng))
- [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://api.github.com/users/jenseng))
- [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://api.github.com/users/waynedpj))
- [#746](https://github.com/wycats/handlebars.js/issues/746) - [CLI] support `handlebars --version` ([@apfelbox](https://api.github.com/users/apfelbox))
- [#747](https://github.com/wycats/handlebars.js/pull/747) - updated grunt-saucelabs, failing tests revealed ([@Jonahss](https://api.github.com/users/Jonahss))
- [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://github.com/jenseng))
- [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://github.com/jenseng))
- [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://github.com/waynedpj))
- [#746](https://github.com/wycats/handlebars.js/issues/746) - [CLI] support `handlebars --version` ([@apfelbox](https://github.com/apfelbox))
- [#747](https://github.com/wycats/handlebars.js/pull/747) - updated grunt-saucelabs, failing tests revealed ([@Jonahss](https://github.com/Jonahss))
- Make JSON a requirement for the compiler. - 058c0fb
- Temporarily kill the AWS publish CI step - 8347ee2
@@ -31,26 +467,26 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2)
## v2.0.0-alpha.1 - February 10th, 2014
- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker))
- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker))
- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg))
- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker))
- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker))
- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach))
- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker))
- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker))
- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker))
- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://github.com/kpdecker))
- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://github.com/kpdecker))
- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://github.com/kayleg))
- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://github.com/kpdecker))
- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://github.com/kpdecker))
- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://github.com/dbachrach))
- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://github.com/kpdecker))
- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://github.com/kpdecker))
- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://github.com/kpdecker))
- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor))
- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg))
- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS))
- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43))
- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker))
- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden))
- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez))
- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://github.com/xibxor))
- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://github.com/stepancheg))
- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://github.com/AAS))
- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://github.com/osiris43))
- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://github.com/kpdecker))
- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://github.com/craigteegarden))
- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://github.com/scottgonzalez))
- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey))
- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey))
- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://github.com/blakeembrey))
- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://github.com/blakeembrey))
Compatibility notes:
- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`.
@@ -60,17 +496,17 @@ Compatibility notes:
- INTEGER -> NUMBER
- Additional PartialNode hash parameter
- New RawBlockNode type
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons.
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatibility reasons.
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
## v1.3.0 - January 1st, 2014
- [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty))
- [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://api.github.com/users/nateirwin))
- [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://api.github.com/users/fivetanley))
- [#695](https://github.com/wycats/handlebars.js/pull/695) - Pull options out from param setup to allow easier extension ([@blakeembrey](https://api.github.com/users/blakeembrey))
- [#694](https://github.com/wycats/handlebars.js/pull/694) - Make the environment reusable ([@blakeembrey](https://api.github.com/users/blakeembrey))
- [#636](https://github.com/wycats/handlebars.js/issues/636) - Print line and column of errors ([@sgronblo](https://api.github.com/users/sgronblo))
- [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://github.com/machty))
- [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://github.com/nateirwin))
- [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://github.com/fivetanley))
- [#695](https://github.com/wycats/handlebars.js/pull/695) - Pull options out from param setup to allow easier extension ([@blakeembrey](https://github.com/blakeembrey))
- [#694](https://github.com/wycats/handlebars.js/pull/694) - Make the environment reusable ([@blakeembrey](https://github.com/blakeembrey))
- [#636](https://github.com/wycats/handlebars.js/issues/636) - Print line and column of errors ([@sgronblo](https://github.com/sgronblo))
- Use literal for data lookup - c1a93d3
- Add stack handling sanity checks - cd885bf
- Fix stack id "leak" on replaceStack - ddfe457
@@ -79,25 +515,25 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.1...v1.3.0)
## v1.2.1 - December 26th, 2013
- [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://api.github.com/users/blakeembrey))
- [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://api.github.com/users/kpdecker))
- [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://github.com/blakeembrey))
- [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://github.com/kpdecker))
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...v1.2.1)
## v1.2.0 - December 23rd, 2013
- [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://api.github.com/users/erwinw))
- [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://api.github.com/users/hamzaCM))
- [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://api.github.com/users/niki4810))
- [#657](https://github.com/wycats/handlebars.js/pull/657) - Fixes issue where cli compiles non handlebars templates ([@chrishoage](https://api.github.com/users/chrishoage))
- [#681](https://github.com/wycats/handlebars.js/pull/681) - Adds in-browser testing and Saucelabs CI ([@kpdecker](https://api.github.com/users/kpdecker))
- [#661](https://github.com/wycats/handlebars.js/pull/661) - Add @first and @index to #each object iteration ([@cgp](https://api.github.com/users/cgp))
- [#650](https://github.com/wycats/handlebars.js/pull/650) - Handlebars is MIT-licensed ([@thomasboyt](https://api.github.com/users/thomasboyt))
- [#641](https://github.com/wycats/handlebars.js/pull/641) - Document ember testing process ([@kpdecker](https://api.github.com/users/kpdecker))
- [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://github.com/erwinw))
- [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://github.com/hamzaCM))
- [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://github.com/niki4810))
- [#657](https://github.com/wycats/handlebars.js/pull/657) - Fixes issue where cli compiles non handlebars templates ([@chrishoage](https://github.com/chrishoage))
- [#681](https://github.com/wycats/handlebars.js/pull/681) - Adds in-browser testing and Saucelabs CI ([@kpdecker](https://github.com/kpdecker))
- [#661](https://github.com/wycats/handlebars.js/pull/661) - Add @first and @index to #each object iteration ([@cgp](https://github.com/cgp))
- [#650](https://github.com/wycats/handlebars.js/pull/650) - Handlebars is MIT-licensed ([@thomasboyt](https://github.com/thomasboyt))
- [#641](https://github.com/wycats/handlebars.js/pull/641) - Document ember testing process ([@kpdecker](https://github.com/kpdecker))
- [#662](https://github.com/wycats/handlebars.js/issues/662) - handlebars-source 1.1.2 is missing from RubyGems.
- [#656](https://github.com/wycats/handlebars.js/issues/656) - Expose COMPILER_REVISION checks as a hook ([@machty](https://api.github.com/users/machty))
- [#668](https://github.com/wycats/handlebars.js/issues/668) - Consider publishing handlebars-runtime as a separate module on npm ([@dlmanning](https://api.github.com/users/dlmanning))
- [#679](https://github.com/wycats/handlebars.js/issues/679) - Unable to override invokePartial ([@mattbrailsford](https://api.github.com/users/mattbrailsford))
- [#646](https://github.com/wycats/handlebars.js/pull/646) - Fix "\\{{" immediately following "\{{" ([@dmarcotte](https://api.github.com/users/dmarcotte))
- [#656](https://github.com/wycats/handlebars.js/issues/656) - Expose COMPILER_REVISION checks as a hook ([@machty](https://github.com/machty))
- [#668](https://github.com/wycats/handlebars.js/issues/668) - Consider publishing handlebars-runtime as a separate module on npm ([@dlmanning](https://github.com/dlmanning))
- [#679](https://github.com/wycats/handlebars.js/issues/679) - Unable to override invokePartial ([@mattbrailsford](https://github.com/mattbrailsford))
- [#646](https://github.com/wycats/handlebars.js/pull/646) - Fix "\\{{" immediately following "\{{" ([@dmarcotte](https://github.com/dmarcotte))
- Allow extend to work with non-prototyped objects - eb53f2e
- Add JavascriptCompiler public API tests - 1a751b2
- Add AST test coverage for more complex paths - ddea5be
@@ -112,8 +548,8 @@ Compatibility notes:
## v1.1.2 - November 5th, 2013
- [#645](https://github.com/wycats/handlebars.js/issues/645) - 1.1.1 fails under IE8 ([@kpdecker](https://api.github.com/users/kpdecker))
- [#644](https://github.com/wycats/handlebars.js/issues/644) - Using precompiled templates (AMD mode) with handlebars.runtime 1.1.1 ([@fddima](https://api.github.com/users/fddima))
- [#645](https://github.com/wycats/handlebars.js/issues/645) - 1.1.1 fails under IE8 ([@kpdecker](https://github.com/kpdecker))
- [#644](https://github.com/wycats/handlebars.js/issues/644) - Using precompiled templates (AMD mode) with handlebars.runtime 1.1.1 ([@fddima](https://github.com/fddima))
- Add simple binary utility tests - 96a45a4
- Fix empty string compilation - eea708a
@@ -130,13 +566,13 @@ Compatibility notes:
## v1.1.0 - November 3rd, 2013
- [#628](https://github.com/wycats/handlebars.js/pull/628) - Convert code to ES6 modules ([@kpdecker](https://api.github.com/users/kpdecker))
- [#336](https://github.com/wycats/handlebars.js/pull/336) - Add whitespace control syntax ([@kpdecker](https://api.github.com/users/kpdecker))
- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probable JIT error under Safari ([@sorentwo](https://api.github.com/users/sorentwo))
- [#483](https://github.com/wycats/handlebars.js/issues/483) - Add first and last @ vars to each helper ([@denniskuczynski](https://api.github.com/users/denniskuczynski))
- [#557](https://github.com/wycats/handlebars.js/pull/557) - `\\{{foo}}` escaping only works in some situations ([@dmarcotte](https://api.github.com/users/dmarcotte))
- [#552](https://github.com/wycats/handlebars.js/pull/552) - Added BOM removal flag. ([@blessenm](https://api.github.com/users/blessenm))
- [#543](https://github.com/wycats/handlebars.js/pull/543) - publish passing master builds to s3 ([@fivetanley](https://api.github.com/users/fivetanley))
- [#628](https://github.com/wycats/handlebars.js/pull/628) - Convert code to ES6 modules ([@kpdecker](https://github.com/kpdecker))
- [#336](https://github.com/wycats/handlebars.js/pull/336) - Add whitespace control syntax ([@kpdecker](https://github.com/kpdecker))
- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probable JIT error under Safari ([@sorentwo](https://github.com/sorentwo))
- [#483](https://github.com/wycats/handlebars.js/issues/483) - Add first and last @ vars to each helper ([@denniskuczynski](https://github.com/denniskuczynski))
- [#557](https://github.com/wycats/handlebars.js/pull/557) - `\\{{foo}}` escaping only works in some situations ([@dmarcotte](https://github.com/dmarcotte))
- [#552](https://github.com/wycats/handlebars.js/pull/552) - Added BOM removal flag. ([@blessenm](https://github.com/blessenm))
- [#543](https://github.com/wycats/handlebars.js/pull/543) - publish passing master builds to s3 ([@fivetanley](https://github.com/fivetanley))
- [#608](https://github.com/wycats/handlebars.js/issues/608) - Add `includeZero` flag to `if` conditional
- [#498](https://github.com/wycats/handlebars.js/issues/498) - `Handlebars.compile` fails on empty string although a single blank works fine
+5
View File
@@ -0,0 +1,5 @@
import Handlebars = require('handlebars')
declare module "handlebars/runtime" {
}
+1 -1
View File
@@ -1,3 +1,3 @@
// Create a simple path alias to allow browserify to resolve
// the runtime on a supported path.
module.exports = require('./dist/cjs/handlebars.runtime');
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
+36
View File
@@ -0,0 +1,36 @@
{
"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
}
}
+105
View File
@@ -0,0 +1,105 @@
<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>
+20 -4
View File
@@ -1,3 +1,4 @@
<!doctype html>
<html>
<head>
<title>Mocha</title>
@@ -28,12 +29,17 @@
<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) {
@@ -79,16 +85,26 @@
runner.on('fail', logFailure);
function logFailure(test, err){
// 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;
}
});
}
var flattenTitles = function(test){
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,
@@ -97,7 +113,7 @@
stack: err.stack,
titles: flattenTitles(test)
});
};
}
});
});
};
+1
View File
@@ -0,0 +1 @@
a
+204 -234
View File
@@ -1,276 +1,246 @@
/*global Handlebars, handlebarsEnv, shouldThrow */
describe('ast', function() {
if (!Handlebars.AST) {
return;
}
var LOCATION_INFO = {
last_line: 0,
first_line: 0,
first_column: 0,
last_column: 0
};
var AST = Handlebars.AST;
function testLocationInfoStorage(node){
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
property,
propertiesLen = properties.length,
i;
for (i = 0; i < propertiesLen; i++){
property = properties[0];
equals(node[property], 0);
}
}
describe('MustacheNode', function() {
function testEscape(open, expected) {
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
equals(mustache.escaped, expected);
}
it('should store args', function() {
var id = {isSimple: true},
hash = {},
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false, LOCATION_INFO);
equals(mustache.type, 'mustache');
equals(mustache.hash, hash);
equals(mustache.escaped, true);
equals(mustache.id, id);
equals(mustache.params.length, 1);
equals(mustache.params[0], 'param1');
equals(!!mustache.isHelper, true);
testLocationInfoStorage(mustache);
});
it('should accept token for escape', function() {
testEscape('{{', true);
testEscape('{{~', true);
testEscape('{{#', true);
testEscape('{{~#', true);
testEscape('{{/', true);
testEscape('{{~/', true);
testEscape('{{^', true);
testEscape('{{~^', true);
testEscape('{', true);
testEscape('{', true);
testEscape('{{&', false);
testEscape('{{~&', false);
testEscape('{{{', false);
testEscape('{{~{', false);
});
it('should accept boolean for escape', function() {
testEscape(true, true);
testEscape({}, true);
testEscape(false, false);
testEscape(undefined, false);
});
});
describe('BlockNode', function() {
it('should throw on mustache mismatch (old sexpr-less version)', function() {
shouldThrow(function() {
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
}, Handlebars.Exception, "foo doesn't match bar");
});
describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
});
it('stores location info', function(){
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
{strip: {}}, {strip: {}},
{
strip: {},
path: {original: 'foo'}
},
LOCATION_INFO);
testLocationInfoStorage(block);
});
});
describe('IdNode', function() {
it('should throw on invalid path', function() {
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: '..'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: '.'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: 'this'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
});
it('stores location info', function(){
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
testLocationInfoStorage(idNode);
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
});
});
describe("HashNode", function(){
it('stores location info', function(){
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
testLocationInfoStorage(hash);
});
});
describe("ContentNode", function(){
it('stores location info', function(){
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
testLocationInfoStorage(content);
});
});
describe("CommentNode", function(){
it('stores location info', function(){
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
testLocationInfoStorage(comment);
});
});
describe("NumberNode", function(){
it('stores location info', function(){
var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO);
testLocationInfoStorage(integer);
});
});
describe("StringNode", function(){
it('stores location info', function(){
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
testLocationInfoStorage(string);
});
});
describe("BooleanNode", function(){
it('stores location info', function(){
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
testLocationInfoStorage(bool);
});
});
describe("DataNode", function(){
it('stores location info', function(){
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
testLocationInfoStorage(data);
});
});
describe("PartialNameNode", function(){
it('stores location info', function(){
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
testLocationInfoStorage(pnn);
});
});
describe("PartialNode", function(){
it('stores location info', function(){
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO);
testLocationInfoStorage(pn);
});
});
describe("ProgramNode", function(){
describe("storing location info", function(){
it("stores when `inverse` argument isn't passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
testLocationInfoStorage(pn);
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 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("stores when `inverse` or `stripInverse` arguments passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
testLocationInfoStorage(pn);
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
var clone = {
strip: {},
firstLine: 0,
lastLine: 0,
firstColumn: 0,
lastColumn: 0
};
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
// Assert that the newly created ProgramNode has the same location
// information as the inverse
testLocationInfoStorage(pn.inverse);
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);
});
});
});
describe("Line Numbers", function(){
var ast, statements;
describe('Line Numbers', function() {
var ast, body;
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
equals(node.firstLine, firstLine);
equals(node.lastLine, lastLine);
equals(node.firstColumn, firstColumn);
equals(node.lastColumn, lastColumn);
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
equals(node.loc.start.line, firstLine);
equals(node.loc.start.column, firstColumn);
equals(node.loc.end.line, lastLine);
equals(node.loc.end.column, lastColumn);
}
ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
statements = ast.statements;
/* 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
/* eslint-enable no-multi-spaces */
body = ast.body;
it('gets ContentNode line numbers', function(){
var contentNode = statements[0];
it('gets ContentNode line numbers', function() {
var contentNode = body[0];
testColumns(contentNode, 1, 1, 0, 7);
});
it('gets MustacheNode line numbers', function(){
var mustacheNode = statements[1];
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(){
var secondContentNode = statements[2];
testColumns(secondContentNode, 1, 2, 21, 8);
it('gets line numbers correct when newlines appear', function() {
testColumns(body[2], 1, 2, 21, 8);
});
it('gets MustacheNode line numbers correct across newlines', function(){
var secondMustacheNode = statements[3];
testColumns(secondMustacheNode, 2, 2, 8, 22);
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 = statements[5];
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers of an inverse of a block helper', function(){
var blockHelperNode = statements[5],
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, 6, 13, 0);
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, '');
});
});
describe('partials', function() {
it('marks partial as standalone', function() {
var ast = Handlebars.parse('{{> partial }} ');
equals(ast.body[1].value, '');
});
it('marks indented partial as standalone', function() {
var ast = Handlebars.parse(' {{> partial }} ');
equals(ast.body[0].value, '');
equals(ast.body[1].indent, ' ');
equals(ast.body[2].value, '');
});
it('marks those around content as not standalone', function() {
var ast = Handlebars.parse('a{{> partial }}');
equals(ast.body[0].omit, undefined);
ast = Handlebars.parse('{{> partial }}a');
equals(ast.body[1].omit, undefined);
});
});
describe('comments', 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);
});
});
});
});
+213 -125
View File
@@ -1,217 +1,305 @@
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
global.handlebarsEnv = null;
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() {
shouldCompileTo('{{foo}}', { foo: 'foo' }, '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() {
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("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() {
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 an undefined context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!");
shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye");
it('compiling with a string context', function() {
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
});
it("comments", function() {
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"comments are ignored");
it('compiling with an undefined context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
});
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");
it('comments', function() {
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'comments are ignored');
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
"booleans do not show the contents when false");
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');
});
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('boolean', function() {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
'booleans show the contents when true');
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
'booleans do not show the contents when false');
});
it("newlines", function() {
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('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');
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, '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);
}
},
'true true object');
shouldCompileTo('{{undefined}}',
{
'undefined': function() {
return 'undefined!';
}
},
'undefined!');
shouldCompileTo('{{null}}',
{
'null': function() {
return 'null!';
}
},
'null!');
});
it('newlines', function() {
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
});
it("escaping text", function() {
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");
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 expressions", function() {
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
it('escaping expressions', function() {
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with 3 handlebars aren't escaped");
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with {{& handlebars aren't escaped");
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
"by default expressions should be escaped");
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped');
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");
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');
});
it("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
"functions returning safestrings aren't escaped");
});
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() {
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 with context argument", function() {
shouldCompileTo("{{awesome frank}}",
it('functions with context argument', function() {
shouldCompileTo('{{awesome frank}}',
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
});
it("pathed functions with context argument", function() {
shouldCompileTo("{{bar.awesome frank}}",
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");
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
});
it("depthed functions with context argument", function() {
shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}",
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");
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
});
it("block functions with context argument", function() {
shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
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");
'inner 1', 'block functions are called with context and options');
});
it("depthed block functions with context argument", function() {
shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
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");
'inner 1', 'block functions are called with context and options');
});
it("block functions without context argument", function() {
shouldCompileTo("{{#awesome}}inner{{/awesome}}",
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");
'inner', 'block functions are called with options');
});
it("pathed block functions without context argument", function() {
shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}",
{foo: {awesome: function(options) { return 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(options) { 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("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('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("nested paths", function() {
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
"Goodbye beautiful world!", "Nested paths access nested objects");
it('nested paths', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Nested paths access nested objects');
});
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('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("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('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('literal references', function() {
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
"Goodbye beautiful world!", "Literal paths can be used");
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("that current context path ({{.}}) doesn't hit helpers", function() {
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
});
it("complex but empty paths", function() {
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
shouldCompileTo("{{person/name}}", {person: {}}, "");
it('complex but empty paths', function() {
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
shouldCompileTo('{{person/name}}', {person: {}}, '');
});
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('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');
string = "{{#hellos}}{{this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
string = '{{#hellos}}{{this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
});
it("this keyword nested inside path", function() {
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
it('this keyword nested inside path', function() {
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
}, Error, 'Invalid path: text/this - 1:13');
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
});
it("this keyword in helpers", function() {
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");
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");
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("this keyword nested inside helpers param", function() {
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
it('this keyword nested inside helpers param', function() {
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
}, Error, 'Invalid path: text/this - 1:17');
shouldCompileTo(
'{{foo [this]}}',
{foo: function(value) { return value; }, 'this': 'bar'},
'bar');
shouldCompileTo(
'{{foo text/[this]}}',
{foo: function(value) { return value; }, text: {'this': 'bar'}},
'bar');
});
it('pass string literals', function() {
shouldCompileTo('{{"foo"}}', {}, '');
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
});
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('pass boolean literals', function() {
shouldCompileTo('{{true}}', {}, '');
shouldCompileTo('{{true}}', { '': 'foo' }, '');
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
});
it('should handle literals in subexpression', function() {
var helpers = {
foo: function(arg) {
return arg;
}
};
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
});
});
+310 -49
View File
@@ -1,86 +1,347 @@
/*global CompilerContext, shouldCompileTo */
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");
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');
});
it("array with @index", function() {
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "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 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("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");
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
});
it("block with complex lookup", function() {
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
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, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
"Templates can access variables in contexts up the stack with relative path syntax");
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'Arrays ignore the contents when empty');
});
it("block with complex lookup using nested context", function() {
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
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('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('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 deep nested complex lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
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 OMG!");
shouldCompileTo(string, hash, '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');
});
describe('inverted sections', function() {
it("inverted sections with unset value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
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.");
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
});
it("inverted section with false value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
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.");
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
});
it("inverted section with empty set", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
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.");
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
});
it("block inverted sections", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
"No people");
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 with empty arrays", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
"No people");
it('block inverted sections with empty arrays', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
'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');
});
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 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('should handle nesting', function() {
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '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!');
});
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 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 ');
});
});
describe('decorators', function() {
it('should apply mustache decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
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');
});
it('should apply block decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
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');
});
it('should apply multiple decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
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');
});
it('should access parent variables', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
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');
});
it('should work with root program', function() {
var run;
var decorators = {
decorator: function(fn, props, container, options) {
equals(options.args[0], 'success');
run = true;
return fn;
}
};
shouldCompileTo(
'{{*decorator "success"}}',
{hash: {'foo': 'success'}, decorators: decorators},
'');
equals(run, true);
});
it('should fail when accessing variables from root', function() {
var run;
var decorators = {
decorator: function(fn, props, container, options) {
equals(options.args[0], undefined);
run = true;
return fn;
}
};
shouldCompileTo(
'{{*decorator foo}}',
{hash: {'foo': 'fail'}, decorators: decorators},
'');
equals(run, true);
});
describe('registration', function() {
it('unregisters', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator('foo', function() {
return 'fail';
});
equals(!!handlebarsEnv.decorators.foo, true);
handlebarsEnv.unregisterDecorator('foo');
equals(handlebarsEnv.decorators.foo, undefined);
});
it('allows multiple globals', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator({
foo: function() {},
bar: function() {}
});
equals(!!handlebarsEnv.decorators.foo, true);
equals(!!handlebarsEnv.decorators.bar, true);
handlebarsEnv.unregisterDecorator('foo');
handlebarsEnv.unregisterDecorator('bar');
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');
});
});
});
});
+325 -110
View File
@@ -1,48 +1,66 @@
/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */
describe('builtin helpers', function() {
describe('#if', function() {
it("if", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
"if with boolean argument shows the contents when true");
shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
"if with string argument shows the contents");
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
"if with boolean argument does not show the contents when false");
shouldCompileTo(string, {world: "world"}, "cruel world!",
"if with undefined does not show the contents");
shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
"if with non-empty array shows the contents");
shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
"if with empty array does not show the contents");
shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!",
"if with zero does not show the contents");
shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
{goodbye: 0, world: "world"}, "GOODBYE cruel world!",
"if with zero does not show the contents");
it('if', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
'if with boolean argument shows the contents when true');
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
'if with string argument shows the contents');
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
'if with boolean argument does not show the contents when false');
shouldCompileTo(string, {world: 'world'}, 'cruel world!',
'if with undefined does not show the contents');
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
'if with non-empty array shows the contents');
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
'if with empty array does not show the contents');
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
'if with zero does not show the contents');
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
'if with zero does not show the contents');
});
it("if with function argument", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns true");
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns string");
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns false");
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns undefined");
it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns true');
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns string');
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns false');
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns undefined');
});
it('should not change the depth list', function() {
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
});
});
describe('#with', function() {
it("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
it('with', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
});
it("with with function argument", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
it('with with function argument', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
});
it('with with else', function() {
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
shouldCompileTo(string, {}, 'Person is not present');
});
it('with provides block parameter', function() {
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
});
it('works when data is disabled', function() {
var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
var result = template({person: {first: 'Alan', last: 'Johnson'}});
equals(result, 'Alan Johnson');
});
});
@@ -53,123 +71,172 @@ describe('builtin helpers', function() {
});
});
it("each", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with array argument ignores the contents when empty");
it('each', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'each with array argument ignores the contents when empty');
});
it("each with an object and @key", function() {
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
it('each without data', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
hash = {goodbyes: 'cruel', world: 'world'};
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
});
it('each without context', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
shouldCompileTo(string, [,,,, ], 'cruel !');
});
it('each with an object and @key', function() {
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
function Clazz() {
this['<b>#1</b>'] = {text: 'goodbye'};
this[2] = {text: 'GOODBYE'};
}
Clazz.prototype.foo = 'fail';
var hash = {goodbyes: new Clazz(), world: 'world'};
// Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash);
var expected1 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!";
var expected1 = '&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 = '2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
equals(actual === expected1 || actual === expected2, true, "each with object argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with object argument ignores the contents when empty");
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
});
it("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}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");
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
});
it("each with nested @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each with nested @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}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! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
equal(result, '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!', 'The @index variable is used');
});
it("each object with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: {'a': {text: "goodbye"}, b: {text: "Goodbye"}, c: {text: "GOODBYE"}}, world: "world"};
it('each with block params', function() {
var string = '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
var hash = {goodbyes: [{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");
equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
});
it("each with @first", function() {
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each object with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "goodbye! cruel world!", "The @first variable is used");
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
});
it("each with nested @first", function() {
var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
});
it("each object with @first", function() {
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: {'foo': {text: "goodbye"}, bar: {text: "Goodbye"}}, world: "world"};
it('each with nested @first', function() {
var string = '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "goodbye! cruel world!", "The @first variable is used");
equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
});
it("each with @last", function() {
var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each object with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "GOODBYE! cruel world!", "The @last variable is used");
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
});
it("each with nested @last", function() {
var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
it('each with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last variable is used");
equal(result, 'GOODBYE! cruel world!', 'The @last variable is used');
});
it("each with function argument", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array function argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with array function argument ignores the contents when empty");
it('each object with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, 'Goodbye! cruel world!', 'The @last variable is used');
});
it("data passed to helpers", function() {
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
it('each with nested @last', function() {
var string = '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
});
it('each with function argument', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array function argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'each with array function argument ignores the contents when empty');
});
it('each object when last key is an empty string', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {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!', 'Empty string key is not skipped');
});
it('data passed to helpers', function() {
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
var hash = {letters: ['a', 'b', 'c']};
var template = CompilerContext.compile(string);
@@ -181,33 +248,181 @@ describe('builtin helpers', function() {
equal(result, 'a!b!c!', 'should output data');
});
it("each on implicit context", function() {
var string = "{{#each}}{{text}}! {{/each}}cruel world!";
var hash = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];
shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!");
it('each on implicit context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
template({});
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
});
});
it("#log", function() {
var string = "{{log blah}}";
var hash = { blah: "whee" };
describe('#log', function() {
/* eslint-disable no-console */
if (typeof console === 'undefined') {
return;
}
var levelArg, logArg;
handlebarsEnv.log = function(level, arg){
levelArg = level;
logArg = arg;
};
var $log,
$info,
$error;
beforeEach(function() {
$log = console.log;
$info = console.info;
$error = console.error;
});
afterEach(function() {
console.log = $log;
console.info = $info;
console.error = $error;
});
shouldCompileTo(string, hash, "", "log should not display");
equals(1, levelArg, "should call log with 1");
equals("whee", logArg, "should call log with 'whee'");
it('should call logger at default level', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' };
var levelArg, logArg;
handlebarsEnv.log = function(level, arg) {
levelArg = level;
logArg = arg;
};
shouldCompileTo(string, hash, '', 'log should not display');
equals(1, levelArg, 'should call log with 1');
equals('whee', logArg, "should call log with 'whee'");
});
it('should call logger at data level', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' };
var levelArg, logArg;
handlebarsEnv.log = function(level, arg) {
levelArg = level;
logArg = arg;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
equals('03', levelArg);
equals('whee', logArg);
});
it('should output to info', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' };
var called;
console.info = function(info) {
equals('whee', info);
called = true;
console.info = $info;
console.log = $log;
};
console.log = function(log) {
equals('whee', log);
called = true;
console.info = $info;
console.log = $log;
};
shouldCompileTo(string, hash, '');
equals(true, called);
});
it('should log at data level', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' };
var called;
console.error = function(log) {
equals('whee', log);
called = true;
console.error = $error;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
equals(true, called);
});
it('should handle missing logger', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' },
called = false;
console.error = undefined;
console.log = function(log) {
equals('whee', log);
called = true;
console.log = $log;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
equals(true, called);
});
it('should handle string log levels', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' };
var called;
console.error = function(log) {
equals('whee', log);
called = true;
};
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
equals(true, called);
called = false;
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
equals(true, called);
});
it('should handle hash log levels', function() {
var string = '{{log blah level="error"}}';
var hash = { blah: 'whee' };
var called;
console.error = function(log) {
equals('whee', log);
called = true;
};
shouldCompileTo(string, hash, '');
equals(true, called);
});
it('should handle hash log levels', function() {
var string = '{{log blah level="debug"}}';
var hash = { blah: 'whee' };
var called = false;
console.info = console.log = console.error = console.debug = function() {
called = true;
console.info = console.log = console.error = console.debug = $log;
};
shouldCompileTo(string, hash, '');
equals(false, called);
});
it('should pass multiple log arguments', function() {
var string = '{{log blah "foo" 1}}';
var hash = { blah: 'whee' };
var called;
console.info = console.log = function(log1, log2, log3) {
equals('whee', log1);
equals('foo', log2);
equals(1, log3);
called = true;
console.log = $log;
};
shouldCompileTo(string, hash, '');
equals(true, called);
});
/* eslint-enable no-console */
});
describe('#lookup', function() {
it('should lookup arbitrary content', function() {
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -216,7 +431,7 @@ describe('builtin helpers', function() {
});
it('should not fail on undefined value', function() {
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
var template = CompilerContext.compile(string);
var result = template(hash);
+111
View File
@@ -0,0 +1,111 @@
describe('compiler', function() {
if (!Handlebars.compile) {
return;
}
describe('#equals', function() {
function compile(string) {
var ast = Handlebars.parse(string);
return new Handlebars.Compiler().compile(ast, {});
}
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);
});
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);
});
});
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() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
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');
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
// Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers.
equal(err.column, 5, 'Checking error column');
}
equal(err.lineNumber, 2, 'Checking error row');
}
});
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.');
} catch (err) {
equal(err.propertyIsEnumerable('column'), true, 'Checking error column');
}
});
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() {
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('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]');
});
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() {
equal(/return ""/.test(Handlebars.precompile('')), true);
});
});
});
+94 -80
View File
@@ -1,25 +1,24 @@
/*global CompilerContext, Handlebars, handlebarsEnv, shouldThrow */
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});
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) {
return options.data.adjective + " " + this.noun;
return options.data.adjective + ' ' + this.noun;
}
};
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy cat", result, "Data output by helper");
var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
equals('happy cat', result, 'Data output by helper');
});
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() {
var template = CompilerContext.compile('{{@hello}}');
var result = template({}, { data: { hello: 'hello' } });
equals('hello', result, '@foo retrieves template data');
});
it("deep @foo triggers automatic top-level data", function() {
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}}');
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
@@ -28,196 +27,211 @@ describe('data', function() {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
if (prop in options.hash) {
frame[prop] = options.hash[prop];
}
}
return options.fn(this, { data: frame });
};
var result = template({ foo: true }, { helpers: helpers });
equals("Hello world", result, "Automatic data was triggered");
equals('Hello world', result, 'Automatic data was triggered');
});
it("parameter data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello @world}}");
it('parameter data can be looked up via @foo', function() {
var template = CompilerContext.compile('{{hello @world}}');
var helpers = {
hello: function(noun) {
return "Hello " + noun;
return 'Hello ' + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
});
it("hash values can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello noun=@world}}");
it('hash values can be looked up via @foo', function() {
var template = CompilerContext.compile('{{hello noun=@world}}');
var helpers = {
hello: function(options) {
return "Hello " + options.hash.noun;
return 'Hello ' + options.hash.noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
});
it("nested parameter data can be looked up via @foo.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
it('nested parameter data can be looked up via @foo.bar', function() {
var template = CompilerContext.compile('{{hello @world.bar}}');
var helpers = {
hello: function(noun) {
return "Hello " + noun;
return 'Hello ' + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
var result = template({}, { helpers: helpers, data: { world: {bar: 'world' } } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
});
it("nested parameter data does not fail with @world.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
it('nested parameter data does not fail with @world.bar', function() {
var template = CompilerContext.compile('{{hello @world.bar}}');
var helpers = {
hello: function(noun) {
return "Hello " + noun;
return 'Hello ' + noun;
}
};
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
var result = template({}, { helpers: helpers, data: { foo: {bar: 'world' } } });
equals('Hello undefined', result, '@foo as a parameter retrieves template data');
});
it("parameter data throws when using complex scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
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("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 });
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 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) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
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");
var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
equals('2hello world1', result, 'data variables are inherited downstream');
});
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
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 = {
my_partial: CompilerContext.compile("{{hello}}", {data: true})
myPartial: CompilerContext.compile('{{hello}}', {data: true})
};
var helpers = {
hello: function(options) {
return options.data.adjective + " " + this.noun;
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");
var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
equals('happy cat', result, 'Data output by helper inside partial');
});
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});
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) {
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
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");
var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
equals('happy world!', result, 'Data output by helper');
});
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});
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) {
return options.fn(this);
},
world: function(options) {
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
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");
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
equals('happy world!', result, 'Data output by helper');
});
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});
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: "?"});
return options.fn({exclaim: '?'});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
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");
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
equals('happy world?', result, 'Data output by helper');
});
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});
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: "?"});
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
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");
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy', accessData: '#win'}});
equals('#win happy world?', result, 'Data output by helper');
});
it("you can override inherited data when invoking a helper", function() {
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
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"} });
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
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");
var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
equals('sad world?', result, 'Overriden data output by helper');
});
it("you can override inherited data when invoking a helper with depth", function() {
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
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"} });
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
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");
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
equals('sad world?', result, 'Overriden data output by helper');
});
describe('@root', function() {
+13 -5
View File
@@ -1,14 +1,20 @@
/*global handlebarsEnv */
require('./common');
var _ = require('underscore'),
fs = require('fs'),
var fs = require('fs'),
vm = require('vm');
global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
global.Handlebars = 'no-conflict';
var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
browser: true,
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
@@ -19,10 +25,12 @@ global.CompilerContext = {
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+41 -12
View File
@@ -1,31 +1,60 @@
var global = (function() { return this; }());
var AssertError;
if (Error.captureStackTrace) {
AssertError = function AssertError(message, caller) {
Error.prototype.constructor.call(this, message);
this.message = message;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, caller || AssertError);
}
};
AssertError.prototype = new Error();
} else {
AssertError = Error;
}
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
};
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) {
throw new Error("'" + expected + "' should === '" + result + "': " + message);
throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
}
};
global.compileWithPartials = function(string, hashOrArray, partials) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
var template,
ary,
options;
if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash;
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
ary = [];
ary.push(hashOrArray[0]);
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4];
}
} else {
ary = [hashOrArray];
}
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
return template.apply(this, ary);
};
global.equals = global.equal = function(a, b, msg) {
global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) {
throw new Error("'" + b + "' should === '" + a + "'" + (msg ? ": " + msg : ''));
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
}
};
@@ -34,15 +63,15 @@ global.shouldThrow = function(callback, type, msg) {
try {
callback();
failed = true;
} catch (err) {
if (type && !(err instanceof type)) {
throw new Error('Type failure');
} catch (caught) {
if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught);
}
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
throw new Error('Message failure');
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
}
}
if (failed) {
throw new Error('It failed to throw');
throw new AssertError('It failed to throw', shouldThrow);
}
};
+2 -1
View File
@@ -1,4 +1,3 @@
/*global handlebarsEnv */
require('./common');
global.Handlebars = require('../../lib');
@@ -14,10 +13,12 @@ global.CompilerContext = {
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+23 -6
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
var fs = require('fs'),
Mocha = require('mocha'),
path = require('path');
@@ -6,19 +7,35 @@ var errors = 0,
testDir = path.dirname(__dirname),
grep = process.argv[2];
var files = [ testDir + "/basic.js" ];
// Lazy hack, but whatever
if (grep === '--min') {
global.minimizedTest = true;
grep = undefined;
}
var files = fs.readdirSync(testDir)
.filter(function(name) { return (/.*\.js$/).test(name); })
.map(function(name) { return testDir + '/' + name; });
.map(function(name) { return testDir + path.sep + name; });
run('./node', function() {
run('./browser', function() {
run('./runtime', function() {
if (global.minimizedTest) {
run('./runtime', function() {
run('./browser', function() {
/* eslint-disable no-process-exit */
process.exit(errors);
/* eslint-enable no-process-exit */
});
});
});
} else {
run('./runtime', function() {
run('./browser', function() {
run('./node', function() {
/* eslint-disable no-process-exit */
process.exit(errors);
/* eslint-enable no-process-exit */
});
});
});
}
function run(env, callback) {
+17 -10
View File
@@ -1,22 +1,27 @@
/*global handlebarsEnv */
require('./common');
var _ = require('underscore'),
fs = require('fs'),
var fs = require('fs'),
vm = require('vm');
global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
global.Handlebars = 'no-conflict';
var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js';
}
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler')['default'];
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
global.CompilerContext = {
browser: true,
compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(template, options) {
return compiler.precompile(template, options, handlebarsEnv);
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
@@ -27,8 +32,8 @@ global.CompilerContext = {
},
compileWithPartial: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.compile = function(template, options) {
return compiler.compile(template, options, handlebarsEnv);
handlebarsEnv.compile = function(compileTemplate, compileOptions) {
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
@@ -39,10 +44,12 @@ global.CompilerContext = {
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+2 -2
View File
@@ -1,6 +1,6 @@
define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['empty'] = template({"compiler":[5,">= 2.0.0"],"main":function(depth0,helpers,partials,data) {
return "";
return templates['empty'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
return "";
},"useData":true});
});

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