Compare commits
255 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca0d385a6c | |||
| 520e1d5f08 | |||
| 02423780a9 | |||
| be92d2f254 | |||
| 443a613b3a | |||
| 83ee5908f2 | |||
| 8dc3d2517b | |||
| 668c4fb878 | |||
| c65c6cce3f | |||
| 3d3796c1e9 | |||
| 075b354a3b | |||
| 30dbf04781 | |||
| e3a54485db | |||
| 8e23642ea2 | |||
| 88ac06875f | |||
| c68bc08a0d | |||
| 6cfbc2653a | |||
| b65135acef | |||
| e2f63da5c0 | |||
| 78e7e28ff9 | |||
| 03d387bf8e | |||
| e0f50b4eec | |||
| 9ed9418488 | |||
| ef0fc290b9 | |||
| edc65b5c19 | |||
| 715f4af179 | |||
| 3bd0fa8b32 | |||
| c295ef085f | |||
| c1ad3c8057 | |||
| af92e32822 | |||
| 2954e7ea66 | |||
| 8eefee56ff | |||
| fd93073146 | |||
| a9a8e40321 | |||
| e66aed5b99 | |||
| 7d4d170ce4 | |||
| eb860c0899 | |||
| b6d3de7123 | |||
| f058970169 | |||
| 77825f8d35 | |||
| 3789a30955 | |||
| e6ad93ea01 | |||
| 2bf4fc6fd3 | |||
| b64202bc91 | |||
| c2f1e62031 | |||
| 08e9a11a34 | |||
| 1fd2edee2a | |||
| 3c9c2f5cf2 | |||
| 16487a088e | |||
| 309d2b49a1 | |||
| 645ac73844 | |||
| b454b02147 | |||
| 7adc19ab40 | |||
| 9dd8d10e12 | |||
| 4671c4b383 | |||
| e46baa1fe2 | |||
| c491b4ea25 | |||
| 738391a064 | |||
| 80c4516fda | |||
| d79212a662 | |||
| b440c38886 | |||
| 2dba7eee3f | |||
| c9789691af | |||
| 9278f217e0 | |||
| d78cc73d3c | |||
| 4de51fe26b | |||
| a32d05f2fc | |||
| ad63f5189f | |||
| 586e672c8b | |||
| f0c6c4cc1f | |||
| a4fd391ba1 | |||
| 9d5aa363cf | |||
| 14ba3d0c43 | |||
| 4cddfe7017 | |||
| f152dfc892 | |||
| 3c1e252169 | |||
| 0d5c807017 | |||
| 1f0834b1a2 | |||
| 575d8772e2 | |||
| 7af1c12db6 | |||
| 91a1b5d2f4 | |||
| 770d746e60 | |||
| d7f0dcf2bb | |||
| 187d611e8c | |||
| d337f40d0e | |||
| d03b6ecfc4 | |||
| 164b7ff0de | |||
| ac4655ee95 | |||
| 14b621caf5 | |||
| 1ec1737d24 | |||
| 1ebce2b53c | |||
| 3a5b65e02b | |||
| dde108e283 | |||
| dc5495216d | |||
| d1fb07b32b | |||
| 04b19848cc | |||
| c40d9f33ad | |||
| 8901c28e9a | |||
| e97685e989 | |||
| e913dc5f12 | |||
| 1f61f21250 | |||
| 587e7a3e63 | |||
| edcc84f292 | |||
| 23d58e79bb | |||
| c819c8b533 | |||
| 827c9d0747 | |||
| f7f05d7558 | |||
| 1988878087 | |||
| 886ba86c2f | |||
| 0817dad7e7 | |||
| 93516a0b07 | |||
| 93e284ed9b | |||
| c02b05fa81 | |||
| 8de121d21c | |||
| 6914090086 | |||
| d54137810a | |||
| c2ac79c970 | |||
| 7ef86173ab | |||
| b75e3e1f40 | |||
| 5e9d17f8fa | |||
| b24797da01 | |||
| a243067883 | |||
| 088e61812a | |||
| 7052e88068 | |||
| b8913fcc65 | |||
| 62ed3c25c7 | |||
| 7fcf9d24f8 | |||
| c76ded8f0f | |||
| a15d01d383 | |||
| 8e1cce7918 | |||
| def79fd5d6 | |||
| fd2e5c983d | |||
| 8d5530ee2c | |||
| 2ab261eab7 | |||
| 143ac806da | |||
| f1752fe66f | |||
| aaab6099f2 | |||
| feb60f85c9 | |||
| 0b593bfe5d | |||
| 2e53fba68f | |||
| ba570c42cf | |||
| 0440af2147 | |||
| b793350fec | |||
| 26d0f7a80d | |||
| b7eada0149 | |||
| b8e769fcb6 | |||
| 9cb31653a4 | |||
| c53f340374 | |||
| 059b330579 | |||
| 8f6047cdfd | |||
| cf7545ef5a | |||
| c958cc8955 | |||
| b250b2d53a | |||
| ff4d827c09 | |||
| e4738491b3 | |||
| 54f7e11b28 | |||
| 8742bde701 | |||
| 2357140c68 | |||
| c5cbeac039 | |||
| 213c0bbe3c | |||
| 050cca0866 | |||
| 2d5579078b | |||
| e3639e2407 | |||
| c2a17c7b30 | |||
| 1266838829 | |||
| 64ecb9ea84 | |||
| 93444c58db | |||
| d148d7cb1f | |||
| a89081d440 | |||
| bba65605b1 | |||
| 06b7224ed9 | |||
| 2078c727c6 | |||
| fff3e40402 | |||
| 35bcd4405f | |||
| 00b4f2ff98 | |||
| e47b5ec192 | |||
| c55a7bed78 | |||
| 164c7ceea4 | |||
| 6ab48d8def | |||
| 8ac20285f6 | |||
| 888750ec27 | |||
| 133b96a2ff | |||
| f119497312 | |||
| 62b64ecc3d | |||
| a57b6824e0 | |||
| a26633f204 | |||
| f9cce4dd02 | |||
| 16572cd848 | |||
| f98c6a5425 | |||
| fde409ace6 | |||
| 9aaace76dc | |||
| 5eb59a5302 | |||
| 10b5fcf92e | |||
| dd0144c771 | |||
| 594f1e30dd | |||
| 871acccbb1 | |||
| cd38583216 | |||
| c454d946e3 | |||
| 9cfb5ddad9 | |||
| be44246221 | |||
| 002561be94 | |||
| 3fb6687013 | |||
| f691db546e | |||
| 25b2e11dd9 | |||
| e5c39375ac | |||
| aef72870b6 | |||
| 684f1032c6 | |||
| 7840ab66a5 | |||
| 4108b8330b | |||
| 445ae12fa4 | |||
| 5cedd62d7b | |||
| 40fb115e53 | |||
| b2e2cfe56d | |||
| 037bfbf4d9 | |||
| 048f2ce7d2 | |||
| b92589a3b0 | |||
| 1c62d4c5d0 | |||
| 7caca944b1 | |||
| 7bd34fb466 | |||
| b02e9a25ee | |||
| f1c8b2e2a2 | |||
| dbc50ac705 | |||
| c6a8fc1c04 | |||
| 42841c41a4 | |||
| 56fc6768d1 | |||
| ee3022228b | |||
| 05e6293bb3 | |||
| 2db0d123c8 | |||
| edc6220d51 | |||
| bacd473fe6 | |||
| 27ac1ee396 | |||
| 78dd89c13a | |||
| 6b87c21fc4 | |||
| 8d22e6f501 | |||
| 3c970cc9c1 | |||
| abba3c7526 | |||
| 4bf1c4ff66 | |||
| 41b6a11d11 | |||
| 2d28f920b0 | |||
| 29b174468d | |||
| d130ed2bc1 | |||
| 2145c14994 | |||
| 8359722e5d | |||
| a1d864d4a7 | |||
| 0ddff8b388 | |||
| 288e986161 | |||
| 30df8a1ac7 | |||
| cda544bca9 | |||
| 69c6ca528d | |||
| a4e39bdfd0 | |||
| b86b9189fa | |||
| d3d39423a3 | |||
| 73d5637564 | |||
| 7729aa956b | |||
| 8947dd077c |
@@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[spec/expected/*.js]
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
.rvmrc
|
||||||
|
.DS_Store
|
||||||
|
/tmp/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
npm-debug.log
|
||||||
|
.idea
|
||||||
|
yarn-error.log
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
|
# Third-party or files that must remain unchanged
|
||||||
|
/spec/expected/
|
||||||
|
/spec/vendor
|
||||||
|
|
||||||
|
# JS-Snippets
|
||||||
|
src/*.js
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {
|
|
||||||
// Possible Errors //
|
|
||||||
//-----------------//
|
|
||||||
|
|
||||||
"comma-dangle": [2, "never"],
|
|
||||||
"no-cond-assign": [2, "except-parens"],
|
|
||||||
|
|
||||||
// Allow for debugging
|
|
||||||
"no-console": 1,
|
|
||||||
|
|
||||||
"no-constant-condition": 2,
|
|
||||||
"no-control-regex": 2,
|
|
||||||
|
|
||||||
// Allow for debugging
|
|
||||||
"no-debugger": 1,
|
|
||||||
|
|
||||||
"no-dupe-args": 2,
|
|
||||||
"no-dupe-keys": 2,
|
|
||||||
"no-duplicate-case": 2,
|
|
||||||
"no-empty": 2,
|
|
||||||
"no-empty-character-class": 2,
|
|
||||||
"no-ex-assign": 2,
|
|
||||||
"no-extra-boolean-cast": 2,
|
|
||||||
"no-extra-parens": 0,
|
|
||||||
"no-extra-semi": 2,
|
|
||||||
"no-func-assign": 0,
|
|
||||||
|
|
||||||
// Stylistic... might consider disallowing in the future
|
|
||||||
"no-inner-declarations": 0,
|
|
||||||
|
|
||||||
"no-invalid-regexp": 2,
|
|
||||||
"no-irregular-whitespace": 2,
|
|
||||||
"no-negated-in-lhs": 2,
|
|
||||||
"no-obj-calls": 2,
|
|
||||||
"no-regex-spaces": 2,
|
|
||||||
"quote-props": [2, "as-needed", {"keywords": true}],
|
|
||||||
"no-sparse-arrays": 0,
|
|
||||||
|
|
||||||
// Optimizer and coverage will handle/highlight this and can be useful for debugging
|
|
||||||
"no-unreachable": 1,
|
|
||||||
|
|
||||||
"use-isnan": 2,
|
|
||||||
"valid-jsdoc": 0,
|
|
||||||
"valid-typeof": 2,
|
|
||||||
|
|
||||||
|
|
||||||
// Best Practices //
|
|
||||||
//----------------//
|
|
||||||
"block-scoped-var": 0,
|
|
||||||
"complexity": 0,
|
|
||||||
"consistent-return": 0,
|
|
||||||
"curly": 2,
|
|
||||||
"default-case": 1,
|
|
||||||
"dot-notation": [2, {"allowKeywords": false}],
|
|
||||||
"eqeqeq": 0,
|
|
||||||
"guard-for-in": 1,
|
|
||||||
"no-alert": 2,
|
|
||||||
"no-caller": 2,
|
|
||||||
"no-div-regex": 1,
|
|
||||||
"no-else-return": 0,
|
|
||||||
"no-empty-label": 2,
|
|
||||||
"no-eq-null": 0,
|
|
||||||
"no-eval": 2,
|
|
||||||
"no-extend-native": 2,
|
|
||||||
"no-extra-bind": 2,
|
|
||||||
"no-fallthrough": 2,
|
|
||||||
"no-floating-decimal": 2,
|
|
||||||
"no-implied-eval": 2,
|
|
||||||
"no-iterator": 2,
|
|
||||||
"no-labels": 2,
|
|
||||||
"no-lone-blocks": 2,
|
|
||||||
"no-loop-func": 2,
|
|
||||||
"no-multi-spaces": 2,
|
|
||||||
"no-multi-str": 1,
|
|
||||||
"no-native-reassign": 2,
|
|
||||||
"no-new": 2,
|
|
||||||
"no-new-func": 2,
|
|
||||||
"no-new-wrappers": 2,
|
|
||||||
"no-octal": 2,
|
|
||||||
"no-octal-escape": 2,
|
|
||||||
"no-param-reassign": 0,
|
|
||||||
"no-process-env": 2,
|
|
||||||
"no-proto": 2,
|
|
||||||
"no-redeclare": 2,
|
|
||||||
"no-return-assign": 2,
|
|
||||||
"no-script-url": 2,
|
|
||||||
"no-self-compare": 2,
|
|
||||||
"no-sequences": 2,
|
|
||||||
"no-throw-literal": 2,
|
|
||||||
"no-unused-expressions": 2,
|
|
||||||
"no-void": 0,
|
|
||||||
"no-warning-comments": 1,
|
|
||||||
"no-with": 2,
|
|
||||||
"radix": 2,
|
|
||||||
"vars-on-top": 0,
|
|
||||||
"wrap-iife": 2,
|
|
||||||
"yoda": 0,
|
|
||||||
|
|
||||||
|
|
||||||
// Strict //
|
|
||||||
//--------//
|
|
||||||
"strict": 0,
|
|
||||||
|
|
||||||
|
|
||||||
// Variables //
|
|
||||||
//-----------//
|
|
||||||
"no-catch-shadow": 2,
|
|
||||||
"no-delete-var": 2,
|
|
||||||
"no-label-var": 2,
|
|
||||||
"no-shadow": 0,
|
|
||||||
"no-shadow-restricted-names": 2,
|
|
||||||
"no-undef": 2,
|
|
||||||
"no-undef-init": 2,
|
|
||||||
"no-undefined": 0,
|
|
||||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
|
|
||||||
"no-use-before-define": [2, "nofunc"],
|
|
||||||
|
|
||||||
|
|
||||||
// Node.js //
|
|
||||||
//---------//
|
|
||||||
// Others left to environment defaults
|
|
||||||
"no-mixed-requires": 0,
|
|
||||||
|
|
||||||
|
|
||||||
// Stylistic //
|
|
||||||
//-----------//
|
|
||||||
"indent": 0,
|
|
||||||
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
|
|
||||||
"camelcase": 2,
|
|
||||||
"comma-spacing": [2, {"before": false, "after": true}],
|
|
||||||
"comma-style": [2, "last"],
|
|
||||||
"consistent-this": [1, "self"],
|
|
||||||
"eol-last": 2,
|
|
||||||
"func-names": 0,
|
|
||||||
"func-style": [2, "declaration"],
|
|
||||||
"key-spacing": [2, {
|
|
||||||
"beforeColon": false,
|
|
||||||
"afterColon": true
|
|
||||||
}],
|
|
||||||
"max-nested-callbacks": 0,
|
|
||||||
"new-cap": 2,
|
|
||||||
"new-parens": 2,
|
|
||||||
"newline-after-var": 0,
|
|
||||||
"no-array-constructor": 2,
|
|
||||||
"no-continue": 0,
|
|
||||||
"no-inline-comments": 0,
|
|
||||||
"no-lonely-if": 2,
|
|
||||||
"no-mixed-spaces-and-tabs": 2,
|
|
||||||
"no-multiple-empty-lines": 0,
|
|
||||||
"no-nested-ternary": 1,
|
|
||||||
"no-new-object": 2,
|
|
||||||
"no-spaced-func": 2,
|
|
||||||
"no-ternary": 0,
|
|
||||||
"no-trailing-spaces": 2,
|
|
||||||
"no-underscore-dangle": 0,
|
|
||||||
"no-extra-parens": [2, "functions"],
|
|
||||||
"one-var": 0,
|
|
||||||
"operator-assignment": 0,
|
|
||||||
"padded-blocks": 0,
|
|
||||||
"quote-props": 0,
|
|
||||||
"quotes": [2, "single", "avoid-escape"],
|
|
||||||
"semi": 2,
|
|
||||||
"semi-spacing": [2, {"before": false, "after": true}],
|
|
||||||
"sort-vars": 0,
|
|
||||||
"space-after-keywords": [2, "always"],
|
|
||||||
"space-before-blocks": [2, "always"],
|
|
||||||
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
|
|
||||||
"space-in-brackets": 0,
|
|
||||||
"space-in-parens": [2, "never"],
|
|
||||||
"space-infix-ops": 2,
|
|
||||||
"space-return-throw-case": 2,
|
|
||||||
"space-unary-ops": 2,
|
|
||||||
"spaced-comment": [2, "always", {"markers": [","]}],
|
|
||||||
"wrap-regex": 1,
|
|
||||||
|
|
||||||
"no-var": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
|
||||||
|
globals: {
|
||||||
|
self: false
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': 'warn',
|
||||||
|
|
||||||
|
// temporarily disabled until the violating places are fixed.
|
||||||
|
'no-func-assign': 'off',
|
||||||
|
'no-sparse-arrays': 'off',
|
||||||
|
|
||||||
|
// Best Practices //
|
||||||
|
//----------------//
|
||||||
|
'default-case': 'warn',
|
||||||
|
'guard-for-in': 'warn',
|
||||||
|
'no-alert': 'error',
|
||||||
|
'no-caller': 'error',
|
||||||
|
'no-div-regex': 'warn',
|
||||||
|
'no-eval': 'error',
|
||||||
|
'no-extend-native': 'error',
|
||||||
|
'no-extra-bind': 'error',
|
||||||
|
'no-floating-decimal': 'error',
|
||||||
|
'no-implied-eval': 'error',
|
||||||
|
'no-iterator': 'error',
|
||||||
|
'no-labels': 'error',
|
||||||
|
'no-lone-blocks': 'error',
|
||||||
|
'no-loop-func': 'error',
|
||||||
|
'no-multi-str': 'warn',
|
||||||
|
'no-global-assign': 'error',
|
||||||
|
'no-new': 'error',
|
||||||
|
'no-new-func': 'error',
|
||||||
|
'no-new-wrappers': 'error',
|
||||||
|
'no-octal-escape': 'error',
|
||||||
|
'no-process-env': 'error',
|
||||||
|
'no-proto': 'error',
|
||||||
|
'no-return-assign': 'error',
|
||||||
|
'no-script-url': 'error',
|
||||||
|
'no-self-compare': 'error',
|
||||||
|
'no-sequences': 'error',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
'no-unused-expressions': 'error',
|
||||||
|
'no-warning-comments': 'warn',
|
||||||
|
'no-with': 'error',
|
||||||
|
radix: 'error',
|
||||||
|
|
||||||
|
// Variables //
|
||||||
|
//-----------//
|
||||||
|
'no-label-var': 'error',
|
||||||
|
'no-undef-init': 'error',
|
||||||
|
'no-use-before-define': ['error', 'nofunc'],
|
||||||
|
|
||||||
|
// ECMAScript 6 //
|
||||||
|
//--------------//
|
||||||
|
'no-var': 'error'
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 6,
|
||||||
|
ecmaFeatures: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/"
|
||||||
|
open-pull-requests-limit: 0
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
allow:
|
||||||
|
- dependency-type: production
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test (Node)
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||||
|
# https://nodejs.org/en/about/releases/
|
||||||
|
node-version: ['10', '12', '14', '16', '18', '20']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
|
- name: Test (Integration)
|
||||||
|
# https://github.com/webpack/webpack/issues/14532
|
||||||
|
if: ${{ matrix.node-version != '18' && matrix.node-version != '20' }}
|
||||||
|
run: |
|
||||||
|
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-test && ./test.sh && cd -
|
||||||
|
|
||||||
|
browser:
|
||||||
|
name: Test (Browser)
|
||||||
|
runs-on: 'ubuntu-20.04'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install Playwright
|
||||||
|
run: |
|
||||||
|
npx playwright install-deps
|
||||||
|
npx playwright install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npx grunt prepare
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test:browser
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-aws-s3:
|
||||||
|
name: Publish to AWS S3
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: |
|
||||||
|
git config --global user.email "release@handlebarsjs.com"
|
||||||
|
git config --global user.name "handlebars-lang"
|
||||||
|
npm run publish:aws
|
||||||
|
env:
|
||||||
|
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||||
|
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||||
+13
-6
@@ -1,12 +1,19 @@
|
|||||||
vendor
|
|
||||||
.rvmrc
|
.rvmrc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
lib/handlebars/compiler/parser.js
|
|
||||||
/dist/
|
|
||||||
/tmp/
|
/tmp/
|
||||||
/coverage/
|
|
||||||
node_modules
|
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
sauce_connect.log*
|
.idea
|
||||||
|
/yarn-error.log
|
||||||
|
/yarn.lock
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
/spec/tmp/*
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "spec/mustache"]
|
[submodule "spec/mustache"]
|
||||||
path = spec/mustache
|
path = spec/mustache
|
||||||
url = git://github.com/mustache/spec.git
|
url = https://github.com/mustache/spec.git
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
instrumentation:
|
|
||||||
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
.gitignore
|
|
||||||
.rvmrc
|
|
||||||
.eslintrc
|
|
||||||
.travis.yml
|
|
||||||
.rspec
|
|
||||||
Gemfile
|
|
||||||
Gemfile.lock
|
|
||||||
Rakefile
|
|
||||||
Gruntfile.js
|
|
||||||
*.gemspec
|
|
||||||
*.nuspec
|
|
||||||
*.log
|
|
||||||
bench/*
|
|
||||||
configurations/*
|
|
||||||
components/*
|
|
||||||
coverage/*
|
|
||||||
dist/cdnjs/*
|
|
||||||
dist/components/*
|
|
||||||
spec/*
|
|
||||||
src/*
|
|
||||||
tasks/*
|
|
||||||
tmp/*
|
|
||||||
publish/*
|
|
||||||
vendor/*
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.rvmrc
|
||||||
|
.DS_Store
|
||||||
|
/tmp/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
npm-debug.log
|
||||||
|
sauce_connect.log*
|
||||||
|
.idea
|
||||||
|
yarn-error.log
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
|
# Third-party or files that must remain unchanged
|
||||||
|
/spec/expected/
|
||||||
|
/spec/vendor
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
before_install:
|
|
||||||
- npm install -g grunt-cli
|
|
||||||
script:
|
|
||||||
- grunt --stack travis
|
|
||||||
email:
|
|
||||||
on_failure: change
|
|
||||||
on_success: never
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
|
||||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
|
||||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- node_js: '5'
|
|
||||||
env:
|
|
||||||
- PUBLISH=true
|
|
||||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
|
||||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
|
||||||
- node_js: '4'
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 100
|
|
||||||
+134
-31
@@ -2,19 +2,26 @@
|
|||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||||
|
|
||||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||||
|
|
||||||
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||||
|
|
||||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||||
|
|
||||||
|
## Branches
|
||||||
|
|
||||||
|
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||||
|
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||||
|
should merge the branch `4.x` into the master branch regularly.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
We also accept [pull requests][pull-request]!
|
We also accept [pull requests][pull-request]!
|
||||||
|
|
||||||
Generally we like to see pull requests that
|
Generally we like to see pull requests that
|
||||||
|
|
||||||
- Maintain the existing code style
|
- 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)
|
- 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 [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||||
@@ -25,8 +32,8 @@ Generally we like to see pull requests that
|
|||||||
|
|
||||||
To build Handlebars.js you'll need a few things installed.
|
To build Handlebars.js you'll need a few things installed.
|
||||||
|
|
||||||
* Node.js
|
- Node.js
|
||||||
* [Grunt](http://gruntjs.com/getting-started)
|
- [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`.
|
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`.
|
||||||
|
|
||||||
@@ -40,16 +47,18 @@ You can also run our set of benchmarks with `grunt bench`.
|
|||||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
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
|
If you notice any problems, please report them to the GitHub issue tracker at
|
||||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||||
|
|
||||||
##Running Tests
|
## Running Tests
|
||||||
|
|
||||||
To run tests locally, first install all dependencies.
|
To run tests locally, first install all dependencies.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Clone the mustache specs into the spec/mustache folder.
|
Clone the mustache specs into the spec/mustache folder.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd spec
|
cd spec
|
||||||
rm -r mustache
|
rm -r mustache
|
||||||
@@ -57,43 +66,137 @@ git clone https://github.com/mustache/spec.git mustache
|
|||||||
```
|
```
|
||||||
|
|
||||||
From the root directory, run the tests.
|
From the root directory, run the tests.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ember testing
|
## Linting and Formatting
|
||||||
|
|
||||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
|
||||||
|
We do linting and formatting in two phases:
|
||||||
|
|
||||||
```sh
|
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||||
npm link
|
while warnings are allowed.
|
||||||
grunt build release
|
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
are forbidden.
|
||||||
|
|
||||||
cd $emberRepoDir
|
You can use the following scripts to make sure that the CI job does not fail:
|
||||||
npm link handlebars
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Releasing
|
- **npm run lint** will run `eslint` and fail on warnings
|
||||||
|
- **npm run format** will run `prettier` on all files
|
||||||
|
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||||
|
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||||
|
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||||
|
|
||||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
## Releasing the latest version
|
||||||
|
|
||||||
A full release may be completed with the following:
|
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||||
|
|
||||||
```
|
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
|
||||||
yo release
|
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
|
||||||
npm publish
|
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
|
||||||
yo release:publish components handlebars.js dist/components/
|
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
|
||||||
|
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
|
||||||
|
|
||||||
cd dist/components/
|
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||||
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 link should be updated to point to the most recent distribution for all instances in our documentation.
|
A full release via Docker may be completed with the following:
|
||||||
|
|
||||||
|
1. Create a `Dockerfile` in this folder for releasing
|
||||||
|
```Dockerfile
|
||||||
|
FROM node:10-slim
|
||||||
|
|
||||||
|
ENV EDITOR=vim
|
||||||
|
|
||||||
|
# Update stretch repositories
|
||||||
|
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
|
||||||
|
-e 's|security.debian.org|archive.debian.org/|g' \
|
||||||
|
-e '/stretch-updates/d' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Install release dependencies
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y git vim
|
||||||
|
|
||||||
|
# Work around deprecated npm dependency install via unauthenticated git-protocol:
|
||||||
|
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
|
||||||
|
RUN git config --system url."https://github.com/".insteadOf git://github.com/
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
RUN git config --system user.email "release@handlebarsjs.com"
|
||||||
|
RUN git config --system user.name "handlebars-lang"
|
||||||
|
|
||||||
|
RUN mkdir /home/node/.config
|
||||||
|
RUN mkdir /home/node/.ssh
|
||||||
|
RUN mkdir /home/node/tmp
|
||||||
|
|
||||||
|
# Generate config for yo generator-release:
|
||||||
|
# https://github.com/kpdecker/generator-release#example
|
||||||
|
# You have to add a valid GitHub OAuth token!
|
||||||
|
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub OAuth token'\n};" > /home/node/.config/generator-release
|
||||||
|
RUN chown -R node:node /home/node/.config
|
||||||
|
|
||||||
|
# Add the generated key to GitHub: https://github.com/settings/keys
|
||||||
|
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
|
||||||
|
RUN chmod 0600 /home/node/.ssh/id_ed25519*
|
||||||
|
RUN chown node:node /home/node/.ssh/id_ed25519*
|
||||||
|
```
|
||||||
|
2. Build and run the Docker image
|
||||||
|
```bash
|
||||||
|
docker build --tag handlebars:release .
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
|
||||||
|
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
|
||||||
|
```
|
||||||
|
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
|
||||||
|
* Add GitHub API token: `vi /home/node/.config/generator-release`
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm install -g yo@1 grunt@1 generator-release
|
||||||
|
npm run release
|
||||||
|
yo release
|
||||||
|
npm login
|
||||||
|
npm publish
|
||||||
|
yo release:publish components handlebars.js dist/components/
|
||||||
|
```
|
||||||
|
6. Publish Ruby `handlebars-source` gem:
|
||||||
|
```bash
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
ruby:3.2-slim bash
|
||||||
|
```
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
cd dist/components/
|
||||||
|
gem build handlebars-source.gemspec
|
||||||
|
gem push handlebars-source-*.gem
|
||||||
|
```
|
||||||
|
|
||||||
|
### After the release
|
||||||
|
|
||||||
|
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](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
|
||||||
|
- [RubyGems](https://rubygems.org/gems/handlebars-source)
|
||||||
|
|
||||||
|
When everything is OK, the **handlebars site** needs to be updated.
|
||||||
|
|
||||||
|
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
|
||||||
|
and make a minimal change to the README. This will invoke a github-action that redeploys
|
||||||
|
the site, fetching the latest version-number from the npm-registry.
|
||||||
|
(note that the default-branch of this repo is not the master and regular changes are done
|
||||||
|
in the `handlebars-lang/docs`-repo).
|
||||||
|
|
||||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||||
[jsfiddle]: https://jsfiddle.net/9D88g/113/
|
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
1. How can I file a bug report:
|
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).
|
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why isn't my Mustache template working?
|
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).
|
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||||
|
|
||||||
1. Why is it slower when compiling?
|
1. Why is it slower when compiling?
|
||||||
|
|
||||||
@@ -36,16 +36,18 @@
|
|||||||
```sh
|
```sh
|
||||||
handlebars --version
|
handlebars --version
|
||||||
```
|
```
|
||||||
|
|
||||||
If using the integrated precompiler and
|
If using the integrated precompiler and
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
console.log(Handlebars.VERSION);
|
console.log(Handlebars.VERSION);
|
||||||
```
|
```
|
||||||
|
|
||||||
On the client side.
|
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.
|
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).
|
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why doesn't IE like the `default` name in the AMD module?
|
1. Why doesn't IE like the `default` name in the AMD module?
|
||||||
|
|
||||||
@@ -53,8 +55,8 @@
|
|||||||
|
|
||||||
1. How do I load the runtime library when using AMD?
|
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.
|
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/handlebars-lang/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.
|
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.
|
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||||
|
|||||||
+90
-90
@@ -1,44 +1,42 @@
|
|||||||
/* eslint-disable no-process-env */
|
/* eslint-disable no-process-env */
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
|
||||||
eslint: {
|
clean: [
|
||||||
options: {
|
'tmp',
|
||||||
},
|
'dist',
|
||||||
files: [
|
'lib/handlebars/compiler/parser.js',
|
||||||
'*.js',
|
'/tests/integration/**/node_modules'
|
||||||
'bench/**/*.js',
|
],
|
||||||
'tasks/**/*.js',
|
|
||||||
'lib/**/!(*.min|parser).js',
|
|
||||||
'spec/**/!(*.amd|json2|require).js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
|
||||||
|
|
||||||
copy: {
|
copy: {
|
||||||
dist: {
|
dist: {
|
||||||
options: {
|
options: {
|
||||||
processContent: function(content) {
|
processContent: function(content) {
|
||||||
return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
|
return (
|
||||||
+ content;
|
grunt.template.process(
|
||||||
|
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||||
|
) + content
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
files: [
|
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
cdnjs: {
|
cdnjs: {
|
||||||
files: [
|
files: [
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
|
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
files: [
|
files: [
|
||||||
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
|
{
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
|
expand: true,
|
||||||
|
cwd: 'components/',
|
||||||
|
src: ['**'],
|
||||||
|
dest: 'dist/components'
|
||||||
|
},
|
||||||
|
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -53,24 +51,28 @@ module.exports = function(grunt) {
|
|||||||
options: {
|
options: {
|
||||||
modules: 'amd'
|
modules: 'amd'
|
||||||
},
|
},
|
||||||
files: [{
|
files: [
|
||||||
expand: true,
|
{
|
||||||
cwd: 'lib/',
|
expand: true,
|
||||||
src: '**/!(index).js',
|
cwd: 'lib/',
|
||||||
dest: 'dist/amd/'
|
src: '**/!(index).js',
|
||||||
}]
|
dest: 'dist/amd/'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
cjs: {
|
cjs: {
|
||||||
options: {
|
options: {
|
||||||
modules: 'common'
|
modules: 'common'
|
||||||
},
|
},
|
||||||
files: [{
|
files: [
|
||||||
cwd: 'lib/',
|
{
|
||||||
expand: true,
|
cwd: 'lib/',
|
||||||
src: '**/!(index).js',
|
expand: true,
|
||||||
dest: 'dist/cjs/'
|
src: '**/!(index).js',
|
||||||
}]
|
dest: 'dist/cjs/'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
@@ -79,7 +81,12 @@ module.exports = function(grunt) {
|
|||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
// 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' }
|
{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader:
|
||||||
|
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@@ -128,15 +135,17 @@ module.exports = function(grunt) {
|
|||||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
||||||
},
|
},
|
||||||
dist: {
|
dist: {
|
||||||
files: [{
|
files: [
|
||||||
cwd: 'dist/',
|
{
|
||||||
expand: true,
|
cwd: 'dist/',
|
||||||
src: ['handlebars*.js', '!*.min.js'],
|
expand: true,
|
||||||
dest: 'dist/',
|
src: ['handlebars*.js', '!*.min.js'],
|
||||||
rename: function(dest, src) {
|
dest: 'dist/',
|
||||||
return dest + src.replace(/\.js$/, '.min.js');
|
rename: function(dest, src) {
|
||||||
|
return dest + src.replace(/\.js$/, '.min.js');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -156,33 +165,10 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'saucelabs-mocha': {
|
|
||||||
all: {
|
shell: {
|
||||||
options: {
|
integrationTests: {
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
command: './tests/integration/run-integration-tests.sh'
|
||||||
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 4,
|
|
||||||
browsers: [
|
|
||||||
{browserName: 'chrome'},
|
|
||||||
{browserName: 'firefox', platform: 'Linux'},
|
|
||||||
{browserName: 'safari', version: 9, platform: 'OS X 10.11'},
|
|
||||||
{browserName: 'safari', version: 8, platform: 'OS X 10.10'},
|
|
||||||
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
|
|
||||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -193,25 +179,11 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||||
tasks: ['build', 'amd', 'tests', 'test']
|
tasks: ['on-file-change']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build a new version of the library
|
|
||||||
this.registerTask('build', 'Builds a distributable version of the current project', [
|
|
||||||
'eslint',
|
|
||||||
'parser',
|
|
||||||
'node',
|
|
||||||
'globals']);
|
|
||||||
|
|
||||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
|
||||||
this.registerTask('node', ['babel:cjs']);
|
|
||||||
this.registerTask('globals', ['webpack']);
|
|
||||||
this.registerTask('tests', ['concat:tests']);
|
|
||||||
|
|
||||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
|
||||||
|
|
||||||
// Load tasks from npm
|
// Load tasks from npm
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
@@ -221,17 +193,45 @@ module.exports = function(grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-babel');
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
grunt.loadNpmTasks('grunt-eslint');
|
grunt.loadNpmTasks('grunt-shell');
|
||||||
grunt.loadNpmTasks('grunt-saucelabs');
|
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
grunt.task.loadTasks('tasks');
|
||||||
|
|
||||||
grunt.registerTask('bench', ['metrics']);
|
grunt.registerTask('node', ['babel:cjs']);
|
||||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
grunt.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||||
|
grunt.registerTask('globals', ['webpack']);
|
||||||
|
grunt.registerTask('release', 'Build final packages', [
|
||||||
|
'uglify',
|
||||||
|
'test:min',
|
||||||
|
'copy:dist',
|
||||||
|
'copy:components',
|
||||||
|
'copy:cdnjs'
|
||||||
|
]);
|
||||||
|
|
||||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
// Requires secret properties from .travis.yaml
|
||||||
|
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
||||||
|
'default',
|
||||||
|
'shell:integrationTests',
|
||||||
|
'metrics',
|
||||||
|
'publish-to-aws'
|
||||||
|
]);
|
||||||
|
|
||||||
|
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
|
||||||
|
|
||||||
|
// === Primary tasks ===
|
||||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||||
|
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||||
|
grunt.registerTask('bench', ['metrics']);
|
||||||
|
grunt.registerTask('prepare', ['build', 'concat:tests']);
|
||||||
|
grunt.registerTask(
|
||||||
|
'build',
|
||||||
|
'Builds a distributable version of the current project',
|
||||||
|
['parser', 'node', 'amd', 'globals']
|
||||||
|
);
|
||||||
|
grunt.registerTask('integration-tests', [
|
||||||
|
'default',
|
||||||
|
'shell:integrationTests'
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2011-2017 by Yehuda Katz
|
Copyright (C) 2011-2019 by Yehuda Katz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
+23
-24
@@ -1,28 +1,29 @@
|
|||||||
[](https://travis-ci.org/wycats/handlebars.js)
|
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||||
[](https://ci.appveyor.com/project/wycats/handlebars-js)
|
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||||
[](https://saucelabs.com/u/handlebars)
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://bundlephobia.com/package/handlebars)
|
||||||
|
[](https://packagephobia.com/result?p=handlebars)
|
||||||
|
|
||||||
Handlebars.js
|
Handlebars.js
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Handlebars.js is an extension to the [Mustache templating
|
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||||
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
|
Checkout the official Handlebars docs site at
|
||||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|
||||||
See our [installation documentation](http://handlebarsjs.com/installation.html).
|
See our [installation documentation](https://handlebarsjs.com/installation/).
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
In general, the syntax of Handlebars.js templates is a superset
|
In general, the syntax of Handlebars.js templates is a superset
|
||||||
of Mustache templates. For basic syntax, check out the [Mustache
|
of Mustache templates. For basic syntax, check out the [Mustache
|
||||||
manpage](http://mustache.github.com/mustache.5.html).
|
manpage](https://mustache.github.io/mustache.5.html).
|
||||||
|
|
||||||
Once you have a template, use the `Handlebars.compile` method to compile
|
Once you have a template, use the `Handlebars.compile` method to compile
|
||||||
the template into a function. The generated function takes a context
|
the template into a function. The generated function takes a context
|
||||||
@@ -46,25 +47,25 @@ var result = template(data);
|
|||||||
// </ul>
|
// </ul>
|
||||||
```
|
```
|
||||||
|
|
||||||
Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
|
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
|
||||||
|
|
||||||
Precompiling Templates
|
Precompiling Templates
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](http://handlebarsjs.com/precompilation.html).
|
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
|
||||||
|
|
||||||
Differences Between Handlebars.js and Mustache
|
Differences Between Handlebars.js and Mustache
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
Handlebars.js adds a couple of additional features to make writing
|
Handlebars.js adds a couple of additional features to make writing
|
||||||
templates easier and also changes a tiny detail of how partials work.
|
templates easier and also changes a tiny detail of how partials work.
|
||||||
|
|
||||||
- [Nested Paths](http://handlebarsjs.com/#paths)
|
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
|
||||||
- [Helpers](http://handlebarsjs.com/#helpers)
|
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
|
||||||
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
|
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
|
||||||
- [Literal Values](http://handlebarsjs.com/#literals)
|
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
||||||
- [Delimited Comments](http://handlebarsjs.com/#comments)
|
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
||||||
|
|
||||||
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
||||||
@@ -89,8 +90,6 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
|
|||||||
Older versions and other runtimes are likely to work but have not been formally
|
Older versions and other runtimes are likely to work but have not been formally
|
||||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
||||||
|
|
||||||
[](https://saucelabs.com/u/handlebars)
|
|
||||||
|
|
||||||
Performance
|
Performance
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -102,18 +101,18 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
|||||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||||
rewritten Handlebars (current version) is faster than the old version,
|
rewritten Handlebars (current version) is faster than the old version,
|
||||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||||
|
|
||||||
|
|
||||||
Upgrading
|
Upgrading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
------------
|
------------
|
||||||
|
|
||||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||||
|
|
||||||
|
|
||||||
Handlebars in the Wild
|
Handlebars in the Wild
|
||||||
@@ -165,4 +164,4 @@ License
|
|||||||
-------
|
-------
|
||||||
Handlebars.js is released under the MIT license.
|
Handlebars.js is released under the MIT license.
|
||||||
|
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
# Test against these versions of Node.js
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- nodejs_version: "4"
|
|
||||||
- nodejs_version: "5"
|
|
||||||
|
|
||||||
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}"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"globals": {
|
|
||||||
"require": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// Disabling for tests, for now.
|
|
||||||
"no-path-concat": 0,
|
|
||||||
|
|
||||||
"no-var": 0,
|
|
||||||
"no-shadow": 0,
|
|
||||||
"handle-callback-err": 0,
|
|
||||||
"no-console": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
|
||||||
handlebars: '{{#names}}{{name}}{{/names}}'
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
|
||||||
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' },
|
|
||||||
handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
|
|
||||||
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
|
|
||||||
eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
|
|
||||||
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
|
|
||||||
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
|
|
||||||
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
|
|
||||||
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
rules: {
|
||||||
|
'no-console': 0,
|
||||||
|
'no-var': 0
|
||||||
|
}
|
||||||
|
};
|
||||||
+159
-111
@@ -1,128 +1,176 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var optimist = require('optimist')
|
var argv = parseArgs({
|
||||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
|
'f': {
|
||||||
'f': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Output File',
|
||||||
'description': 'Output File',
|
'alias': 'output'
|
||||||
'alias': 'output'
|
},
|
||||||
},
|
'map': {
|
||||||
'map': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Source Map File'
|
||||||
'description': 'Source Map File'
|
},
|
||||||
},
|
'a': {
|
||||||
'a': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Exports amd style (require.js)',
|
||||||
'description': 'Exports amd style (require.js)',
|
'alias': 'amd'
|
||||||
'alias': 'amd'
|
},
|
||||||
},
|
'c': {
|
||||||
'c': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
'alias': 'commonjs',
|
||||||
'alias': 'commonjs',
|
'default': null
|
||||||
'default': null
|
},
|
||||||
},
|
'h': {
|
||||||
'h': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
'alias': 'handlebarPath',
|
||||||
'alias': 'handlebarPath',
|
'default': ''
|
||||||
'default': ''
|
},
|
||||||
},
|
'k': {
|
||||||
'k': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Known helpers',
|
||||||
'description': 'Known helpers',
|
'alias': 'known'
|
||||||
'alias': 'known'
|
},
|
||||||
},
|
'o': {
|
||||||
'o': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Known helpers only',
|
||||||
'description': 'Known helpers only',
|
'alias': 'knownOnly'
|
||||||
'alias': 'knownOnly'
|
},
|
||||||
},
|
'm': {
|
||||||
'm': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Minimize output',
|
||||||
'description': 'Minimize output',
|
'alias': 'min'
|
||||||
'alias': 'min'
|
},
|
||||||
},
|
'n': {
|
||||||
'n': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Template namespace',
|
||||||
'description': 'Template namespace',
|
'alias': 'namespace',
|
||||||
'alias': 'namespace',
|
'default': 'Handlebars.templates'
|
||||||
'default': 'Handlebars.templates'
|
},
|
||||||
},
|
's': {
|
||||||
's': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Output template function only.',
|
||||||
'description': 'Output template function only.',
|
'alias': 'simple'
|
||||||
'alias': 'simple'
|
},
|
||||||
},
|
'N': {
|
||||||
'N': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||||
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
'alias': 'name'
|
||||||
'alias': 'name'
|
},
|
||||||
},
|
'i': {
|
||||||
'i': {
|
'type': 'string',
|
||||||
'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.',
|
||||||
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
|
'alias': 'string'
|
||||||
'alias': 'string'
|
},
|
||||||
},
|
'r': {
|
||||||
'r': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Template root. Base value that will be stripped from template names.',
|
||||||
'description': 'Template root. Base value that will be stripped from template names.',
|
'alias': 'root'
|
||||||
'alias': 'root'
|
},
|
||||||
},
|
'p': {
|
||||||
'p': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Compiling a partial template',
|
||||||
'description': 'Compiling a partial template',
|
'alias': 'partial'
|
||||||
'alias': 'partial'
|
},
|
||||||
},
|
'd': {
|
||||||
'd': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Include data when compiling',
|
||||||
'description': 'Include data when compiling',
|
'alias': 'data'
|
||||||
'alias': 'data'
|
},
|
||||||
},
|
'e': {
|
||||||
'e': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Template extension.',
|
||||||
'description': 'Template extension.',
|
'alias': 'extension',
|
||||||
'alias': 'extension',
|
'default': 'handlebars'
|
||||||
'default': 'handlebars'
|
},
|
||||||
},
|
'b': {
|
||||||
'b': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
'alias': 'bom'
|
||||||
'alias': 'bom'
|
},
|
||||||
},
|
'v': {
|
||||||
'v': {
|
'type': 'boolean',
|
||||||
'type': 'boolean',
|
'description': 'Prints the current compiler version',
|
||||||
'description': 'Prints the current compiler version',
|
'alias': 'version'
|
||||||
'alias': 'version'
|
},
|
||||||
},
|
'help': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Outputs this message'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
'help': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Outputs this message'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.wrap(120)
|
|
||||||
.check(function(argv) {
|
|
||||||
if (argv.version) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var argv = optimist.argv;
|
|
||||||
argv.files = argv._;
|
argv.files = argv._;
|
||||||
delete argv._;
|
delete argv._;
|
||||||
|
|
||||||
var Precompiler = require('../dist/cjs/precompiler');
|
var Precompiler = require('../dist/cjs/precompiler');
|
||||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||||
optimist.showHelp();
|
printUsage(argv._spec, 120);
|
||||||
} else {
|
} else {
|
||||||
Precompiler.cli(opts);
|
Precompiler.cli(opts);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function pad(n) {
|
||||||
|
var str = '';
|
||||||
|
while (str.length < n) {
|
||||||
|
str += ' ';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(spec) {
|
||||||
|
var opts = { alias: {}, boolean: [], default: {}, string: [] };
|
||||||
|
|
||||||
|
Object.keys(spec).forEach(function (arg) {
|
||||||
|
var opt = spec[arg];
|
||||||
|
opts[opt.type].push(arg);
|
||||||
|
if ('alias' in opt) opts.alias[arg] = opt.alias;
|
||||||
|
if ('default' in opt) opts.default[arg] = opt.default;
|
||||||
|
});
|
||||||
|
|
||||||
|
var argv = require('minimist')(process.argv.slice(2), opts);
|
||||||
|
argv._spec = spec;
|
||||||
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printUsage(spec, wrap) {
|
||||||
|
var wordwrap = require('wordwrap');
|
||||||
|
|
||||||
|
console.log('Precompile handlebar templates.');
|
||||||
|
console.log('Usage: handlebars [template|directory]...');
|
||||||
|
|
||||||
|
var opts = [];
|
||||||
|
var width = 0;
|
||||||
|
Object.keys(spec).forEach(function (arg) {
|
||||||
|
var opt = spec[arg];
|
||||||
|
|
||||||
|
var name = (arg.length === 1 ? '-' : '--') + arg;
|
||||||
|
if ('alias' in opt) name += ', --' + opt.alias;
|
||||||
|
|
||||||
|
var meta = '[' + opt.type + ']';
|
||||||
|
if ('default' in opt) meta += ' [default: ' + JSON.stringify(opt.default) + ']';
|
||||||
|
|
||||||
|
opts.push({ name: name, desc: opt.description, meta: meta });
|
||||||
|
if (name.length > width) width = name.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Options:');
|
||||||
|
opts.forEach(function (opt) {
|
||||||
|
var desc = wordwrap(width + 4, wrap + 1)(opt.desc);
|
||||||
|
|
||||||
|
console.log(' %s%s%s%s%s',
|
||||||
|
opt.name,
|
||||||
|
pad(width - opt.name.length + 2),
|
||||||
|
desc.slice(width + 4),
|
||||||
|
pad(wrap - opt.meta.length - desc.split(/\n/).pop().length),
|
||||||
|
opt.meta
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.0.11",
|
"version": "4.7.8",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|||||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||||
gem.summary = %q{Handlebars.js source code wrapper}
|
gem.summary = %q{Handlebars.js source code wrapper}
|
||||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||||
gem.version = package["version"].sub "-", "."
|
gem.version = package["version"].sub "-", "."
|
||||||
gem.license = "MIT"
|
gem.license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>4.0.11</version>
|
<version>4.7.8</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Extension of the Mustache logicless template language</description>
|
<description>Extension of the Mustache logicless template language</description>
|
||||||
<releaseNotes></releaseNotes>
|
<releaseNotes></releaseNotes>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "handlebars",
|
||||||
|
"version": "4.7.8",
|
||||||
|
"license": "MIT",
|
||||||
|
"jspm": {
|
||||||
|
"main": "handlebars",
|
||||||
|
"shim": {
|
||||||
|
"handlebars": {
|
||||||
|
"exports": "Handlebars"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"handlebars.js",
|
||||||
|
"handlebars.runtime.js"
|
||||||
|
],
|
||||||
|
"buildConfig": {
|
||||||
|
"minify": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
|
|||||||
Handlebars.precompile(ast);
|
Handlebars.precompile(ast);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
|
||||||
|
There are two primary APIs that are used to parse an existing template into the AST:
|
||||||
|
|
||||||
|
#### parseWithoutProcessing
|
||||||
|
|
||||||
|
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### parse
|
||||||
|
|
||||||
|
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||||
|
|
||||||
|
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||||
|
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||||
|
|
||||||
|
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parse(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
### Basic
|
### Basic
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ Decorators are executed when the block program is instantiated and are passed `(
|
|||||||
|
|
||||||
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||||
|
|
||||||
The [inline partial](https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
// Handlebars should not use node or browser-specific APIs
|
||||||
|
'shared-node-browser': true,
|
||||||
|
node: false,
|
||||||
|
browser: false
|
||||||
|
}
|
||||||
|
};
|
||||||
+6
-1
@@ -2,7 +2,11 @@ import runtime from './handlebars.runtime';
|
|||||||
|
|
||||||
// Compiler imports
|
// Compiler imports
|
||||||
import AST from './handlebars/compiler/ast';
|
import AST from './handlebars/compiler/ast';
|
||||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
import {
|
||||||
|
parser as Parser,
|
||||||
|
parse,
|
||||||
|
parseWithoutProcessing
|
||||||
|
} from './handlebars/compiler/base';
|
||||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||||
import Visitor from './handlebars/compiler/visitor';
|
import Visitor from './handlebars/compiler/visitor';
|
||||||
@@ -25,6 +29,7 @@ function create() {
|
|||||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||||
hb.Parser = Parser;
|
hb.Parser = Parser;
|
||||||
hb.parse = parse;
|
hb.parse = parse;
|
||||||
|
hb.parseWithoutProcessing = parseWithoutProcessing;
|
||||||
|
|
||||||
return hb;
|
return hb;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-10
@@ -1,11 +1,13 @@
|
|||||||
import {createFrame, extend, toString} from './utils';
|
import { createFrame, extend, toString } from './utils';
|
||||||
import Exception from './exception';
|
import Exception from './exception';
|
||||||
import {registerDefaultHelpers} from './helpers';
|
import { registerDefaultHelpers } from './helpers';
|
||||||
import {registerDefaultDecorators} from './decorators';
|
import { registerDefaultDecorators } from './decorators';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
import { resetLoggedProperties } from './internal/proto-access';
|
||||||
|
|
||||||
export const VERSION = '4.0.11';
|
export const VERSION = '4.7.8';
|
||||||
export const COMPILER_REVISION = 7;
|
export const COMPILER_REVISION = 8;
|
||||||
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
export const REVISION_CHANGES = {
|
export const REVISION_CHANGES = {
|
||||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||||
@@ -14,7 +16,8 @@ export const REVISION_CHANGES = {
|
|||||||
4: '== 1.x.x',
|
4: '== 1.x.x',
|
||||||
5: '== 2.0.0-alpha.x',
|
5: '== 2.0.0-alpha.x',
|
||||||
6: '>= 2.0.0-beta.1',
|
6: '>= 2.0.0-beta.1',
|
||||||
7: '>= 4.0.0'
|
7: '>= 4.0.0 <4.3.0',
|
||||||
|
8: '>= 4.3.0'
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectType = '[object Object]';
|
const objectType = '[object Object]';
|
||||||
@@ -36,7 +39,9 @@ HandlebarsEnvironment.prototype = {
|
|||||||
|
|
||||||
registerHelper: function(name, fn) {
|
registerHelper: function(name, fn) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
if (fn) {
|
||||||
|
throw new Exception('Arg not supported with multiple helpers');
|
||||||
|
}
|
||||||
extend(this.helpers, name);
|
extend(this.helpers, name);
|
||||||
} else {
|
} else {
|
||||||
this.helpers[name] = fn;
|
this.helpers[name] = fn;
|
||||||
@@ -51,7 +56,9 @@ HandlebarsEnvironment.prototype = {
|
|||||||
extend(this.partials, name);
|
extend(this.partials, name);
|
||||||
} else {
|
} else {
|
||||||
if (typeof partial === 'undefined') {
|
if (typeof partial === 'undefined') {
|
||||||
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
|
throw new Exception(
|
||||||
|
`Attempting to register a partial called "${name}" as undefined`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.partials[name] = partial;
|
this.partials[name] = partial;
|
||||||
}
|
}
|
||||||
@@ -62,7 +69,9 @@ HandlebarsEnvironment.prototype = {
|
|||||||
|
|
||||||
registerDecorator: function(name, fn) {
|
registerDecorator: function(name, fn) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
|
if (fn) {
|
||||||
|
throw new Exception('Arg not supported with multiple decorators');
|
||||||
|
}
|
||||||
extend(this.decorators, name);
|
extend(this.decorators, name);
|
||||||
} else {
|
} else {
|
||||||
this.decorators[name] = fn;
|
this.decorators[name] = fn;
|
||||||
@@ -70,9 +79,16 @@ HandlebarsEnvironment.prototype = {
|
|||||||
},
|
},
|
||||||
unregisterDecorator: function(name) {
|
unregisterDecorator: function(name) {
|
||||||
delete this.decorators[name];
|
delete this.decorators[name];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Reset the memory of illegal property accesses that have already been logged.
|
||||||
|
* @deprecated should only be used in handlebars test-cases
|
||||||
|
*/
|
||||||
|
resetLoggedPropertyAccesses() {
|
||||||
|
resetLoggedProperties();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export let log = logger.log;
|
export let log = logger.log;
|
||||||
|
|
||||||
export {createFrame, logger};
|
export { createFrame, logger };
|
||||||
|
|||||||
@@ -5,24 +5,28 @@ let AST = {
|
|||||||
// * it is an eligible helper, and
|
// * it is an eligible helper, and
|
||||||
// * it has at least one parameter or hash segment
|
// * it has at least one parameter or hash segment
|
||||||
helperExpression: function(node) {
|
helperExpression: function(node) {
|
||||||
return (node.type === 'SubExpression')
|
return (
|
||||||
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
|
node.type === 'SubExpression' ||
|
||||||
&& !!((node.params && node.params.length) || node.hash));
|
((node.type === 'MustacheStatement' ||
|
||||||
|
node.type === 'BlockStatement') &&
|
||||||
|
!!((node.params && node.params.length) || node.hash))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
scopedId: function(path) {
|
scopedId: function(path) {
|
||||||
return (/^\.|this\b/).test(path.original);
|
return /^\.|this\b/.test(path.original);
|
||||||
},
|
},
|
||||||
|
|
||||||
// an ID is simple if it only has one part, and that part is not
|
// an ID is simple if it only has one part, and that part is not
|
||||||
// `..` or `this`.
|
// `..` or `this`.
|
||||||
simpleId: function(path) {
|
simpleId: function(path) {
|
||||||
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
|
return (
|
||||||
|
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||||
// must modify the object to operate properly.
|
// must modify the object to operate properly.
|
||||||
export default AST;
|
export default AST;
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ export { parser };
|
|||||||
let yy = {};
|
let yy = {};
|
||||||
extend(yy, Helpers);
|
extend(yy, Helpers);
|
||||||
|
|
||||||
export function parse(input, options) {
|
export function parseWithoutProcessing(input, options) {
|
||||||
// Just return if an already-compiled AST was passed in.
|
// Just return if an already-compiled AST was passed in.
|
||||||
if (input.type === 'Program') { return input; }
|
if (input.type === 'Program') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
parser.yy = yy;
|
parser.yy = yy;
|
||||||
|
|
||||||
@@ -19,6 +21,14 @@ export function parse(input, options) {
|
|||||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
let strip = new WhitespaceControl(options);
|
let ast = parser.parse(input);
|
||||||
return strip.accept(parser.parse(input));
|
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(input, options) {
|
||||||
|
let ast = parseWithoutProcessing(input, options);
|
||||||
|
let strip = new WhitespaceControl(options);
|
||||||
|
|
||||||
|
return strip.accept(ast);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/* global define */
|
/* global define, require */
|
||||||
import {isArray} from '../utils';
|
import { isArray } from '../utils';
|
||||||
|
|
||||||
let SourceNode;
|
let SourceNode;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (typeof define !== 'function' || !define.amd) {
|
if (typeof define !== 'function' || !define.amd) {
|
||||||
// We don't support this in AMD environments. For these environments, we asusme that
|
// We don't support this in AMD environments. For these environments, we assume that
|
||||||
// they are running on the browser and thus have no need for the source-map library.
|
// they are running on the browser and thus have no need for the source-map library.
|
||||||
let SourceMap = require('source-map');
|
let SourceMap = require('source-map');
|
||||||
SourceNode = SourceMap.SourceNode;
|
SourceNode = SourceMap.SourceNode;
|
||||||
@@ -38,7 +38,7 @@ if (!SourceNode) {
|
|||||||
this.src = chunks + this.src;
|
this.src = chunks + this.src;
|
||||||
},
|
},
|
||||||
toStringWithSourceMap: function() {
|
toStringWithSourceMap: function() {
|
||||||
return {code: this.toString()};
|
return { code: this.toString() };
|
||||||
},
|
},
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return this.src;
|
return this.src;
|
||||||
@@ -46,7 +46,6 @@ if (!SourceNode) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function castChunk(chunk, codeGen, loc) {
|
function castChunk(chunk, codeGen, loc) {
|
||||||
if (isArray(chunk)) {
|
if (isArray(chunk)) {
|
||||||
let ret = [];
|
let ret = [];
|
||||||
@@ -62,7 +61,6 @@ function castChunk(chunk, codeGen, loc) {
|
|||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function CodeGen(srcFile) {
|
function CodeGen(srcFile) {
|
||||||
this.srcFile = srcFile;
|
this.srcFile = srcFile;
|
||||||
this.source = [];
|
this.source = [];
|
||||||
@@ -94,17 +92,22 @@ CodeGen.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
empty: function() {
|
empty: function() {
|
||||||
let loc = this.currentLocation || {start: {}};
|
let loc = this.currentLocation || { start: {} };
|
||||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||||
},
|
},
|
||||||
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
|
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
|
||||||
if (chunk instanceof SourceNode) {
|
if (chunk instanceof SourceNode) {
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk = castChunk(chunk, this, loc);
|
chunk = castChunk(chunk, this, loc);
|
||||||
|
|
||||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
|
return new SourceNode(
|
||||||
|
loc.start.line,
|
||||||
|
loc.start.column,
|
||||||
|
this.srcFile,
|
||||||
|
chunk
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
functionCall: function(fn, type, params) {
|
functionCall: function(fn, type, params) {
|
||||||
@@ -113,26 +116,28 @@ CodeGen.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
quotedString: function(str) {
|
quotedString: function(str) {
|
||||||
return '"' + (str + '')
|
return (
|
||||||
.replace(/\\/g, '\\\\')
|
'"' +
|
||||||
.replace(/"/g, '\\"')
|
(str + '')
|
||||||
.replace(/\n/g, '\\n')
|
.replace(/\\/g, '\\\\')
|
||||||
.replace(/\r/g, '\\r')
|
.replace(/"/g, '\\"')
|
||||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
.replace(/\n/g, '\\n')
|
||||||
.replace(/\u2029/g, '\\u2029') + '"';
|
.replace(/\r/g, '\\r')
|
||||||
|
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||||
|
.replace(/\u2029/g, '\\u2029') +
|
||||||
|
'"'
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
objectLiteral: function(obj) {
|
objectLiteral: function(obj) {
|
||||||
let pairs = [];
|
let pairs = [];
|
||||||
|
|
||||||
for (let key in obj) {
|
Object.keys(obj).forEach(key => {
|
||||||
if (obj.hasOwnProperty(key)) {
|
let value = castChunk(obj[key], this);
|
||||||
let value = castChunk(obj[key], this);
|
if (value !== 'undefined') {
|
||||||
if (value !== 'undefined') {
|
pairs.push([this.quotedString(key), ':', value]);
|
||||||
pairs.push([this.quotedString(key), ':', value]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
let ret = this.generateList(pairs);
|
let ret = this.generateList(pairs);
|
||||||
ret.prepend('{');
|
ret.prepend('{');
|
||||||
@@ -140,7 +145,6 @@ CodeGen.prototype = {
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
generateList: function(entries) {
|
generateList: function(entries) {
|
||||||
let ret = this.empty();
|
let ret = this.empty();
|
||||||
|
|
||||||
@@ -165,4 +169,3 @@ CodeGen.prototype = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default CodeGen;
|
export default CodeGen;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable new-cap */
|
/* eslint-disable new-cap */
|
||||||
|
|
||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
import {isArray, indexOf, extend} from '../utils';
|
import { isArray, indexOf, extend } from '../utils';
|
||||||
import AST from './ast';
|
import AST from './ast';
|
||||||
|
|
||||||
const slice = [].slice;
|
const slice = [].slice;
|
||||||
@@ -24,8 +24,11 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
let opcode = this.opcodes[i],
|
let opcode = this.opcodes[i],
|
||||||
otherOpcode = other.opcodes[i];
|
otherOpcode = other.opcodes[i];
|
||||||
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
|
if (
|
||||||
|
opcode.opcode !== otherOpcode.opcode ||
|
||||||
|
!argEquals(opcode.args, otherOpcode.args)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,34 +57,28 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
options.blockParams = options.blockParams || [];
|
options.blockParams = options.blockParams || [];
|
||||||
|
|
||||||
// These changes will propagate to the other compiler components
|
options.knownHelpers = extend(
|
||||||
let knownHelpers = options.knownHelpers;
|
Object.create(null),
|
||||||
options.knownHelpers = {
|
{
|
||||||
'helperMissing': true,
|
helperMissing: true,
|
||||||
'blockHelperMissing': true,
|
blockHelperMissing: true,
|
||||||
'each': true,
|
each: true,
|
||||||
'if': true,
|
if: true,
|
||||||
'unless': true,
|
unless: true,
|
||||||
'with': true,
|
with: true,
|
||||||
'log': true,
|
log: true,
|
||||||
'lookup': true
|
lookup: true
|
||||||
};
|
},
|
||||||
if (knownHelpers) {
|
options.knownHelpers
|
||||||
for (let name in knownHelpers) {
|
);
|
||||||
/* istanbul ignore else */
|
|
||||||
if (name in knownHelpers) {
|
|
||||||
this.options.knownHelpers[name] = knownHelpers[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.accept(program);
|
return this.accept(program);
|
||||||
},
|
},
|
||||||
|
|
||||||
compileProgram: function(program) {
|
compileProgram: function(program) {
|
||||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||||
result = childCompiler.compile(program, this.options),
|
result = childCompiler.compile(program, this.options),
|
||||||
guid = this.guid++;
|
guid = this.guid++;
|
||||||
|
|
||||||
this.usePartial = this.usePartial || result.usePartial;
|
this.usePartial = this.usePartial || result.usePartial;
|
||||||
|
|
||||||
@@ -107,7 +104,7 @@ Compiler.prototype = {
|
|||||||
this.options.blockParams.unshift(program.blockParams);
|
this.options.blockParams.unshift(program.blockParams);
|
||||||
|
|
||||||
let body = program.body,
|
let body = program.body,
|
||||||
bodyLength = body.length;
|
bodyLength = body.length;
|
||||||
for (let i = 0; i < bodyLength; i++) {
|
for (let i = 0; i < bodyLength; i++) {
|
||||||
this.accept(body[i]);
|
this.accept(body[i]);
|
||||||
}
|
}
|
||||||
@@ -124,7 +121,7 @@ Compiler.prototype = {
|
|||||||
transformLiteralToPath(block);
|
transformLiteralToPath(block);
|
||||||
|
|
||||||
let program = block.program,
|
let program = block.program,
|
||||||
inverse = block.inverse;
|
inverse = block.inverse;
|
||||||
|
|
||||||
program = program && this.compileProgram(program);
|
program = program && this.compileProgram(program);
|
||||||
inverse = inverse && this.compileProgram(inverse);
|
inverse = inverse && this.compileProgram(inverse);
|
||||||
@@ -159,7 +156,7 @@ Compiler.prototype = {
|
|||||||
DecoratorBlock(decorator) {
|
DecoratorBlock(decorator) {
|
||||||
let program = decorator.program && this.compileProgram(decorator.program);
|
let program = decorator.program && this.compileProgram(decorator.program);
|
||||||
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||||
path = decorator.path;
|
path = decorator.path;
|
||||||
|
|
||||||
this.useDecorators = true;
|
this.useDecorators = true;
|
||||||
this.opcode('registerDecorator', params.length, path.original);
|
this.opcode('registerDecorator', params.length, path.original);
|
||||||
@@ -175,17 +172,20 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
let params = partial.params;
|
let params = partial.params;
|
||||||
if (params.length > 1) {
|
if (params.length > 1) {
|
||||||
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
|
throw new Exception(
|
||||||
|
'Unsupported number of partial arguments: ' + params.length,
|
||||||
|
partial
|
||||||
|
);
|
||||||
} else if (!params.length) {
|
} else if (!params.length) {
|
||||||
if (this.options.explicitPartialContext) {
|
if (this.options.explicitPartialContext) {
|
||||||
this.opcode('pushLiteral', 'undefined');
|
this.opcode('pushLiteral', 'undefined');
|
||||||
} else {
|
} else {
|
||||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
params.push({ type: 'PathExpression', parts: [], depth: 0 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let partialName = partial.name.original,
|
let partialName = partial.name.original,
|
||||||
isDynamic = partial.name.type === 'SubExpression';
|
isDynamic = partial.name.type === 'SubExpression';
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
this.accept(partial.name);
|
this.accept(partial.name);
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,6 @@ Compiler.prototype = {
|
|||||||
this.DecoratorBlock(decorator);
|
this.DecoratorBlock(decorator);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
ContentStatement: function(content) {
|
ContentStatement: function(content) {
|
||||||
if (content.value) {
|
if (content.value) {
|
||||||
this.opcode('appendContent', content.value);
|
this.opcode('appendContent', content.value);
|
||||||
@@ -241,8 +240,8 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||||
let path = sexpr.path,
|
let path = sexpr.path,
|
||||||
name = path.parts[0],
|
name = path.parts[0],
|
||||||
isBlock = program != null || inverse != null;
|
isBlock = program != null || inverse != null;
|
||||||
|
|
||||||
this.opcode('getContext', path.depth);
|
this.opcode('getContext', path.depth);
|
||||||
|
|
||||||
@@ -264,19 +263,27 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
helperSexpr: function(sexpr, program, inverse) {
|
helperSexpr: function(sexpr, program, inverse) {
|
||||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||||
path = sexpr.path,
|
path = sexpr.path,
|
||||||
name = path.parts[0];
|
name = path.parts[0];
|
||||||
|
|
||||||
if (this.options.knownHelpers[name]) {
|
if (this.options.knownHelpers[name]) {
|
||||||
this.opcode('invokeKnownHelper', params.length, name);
|
this.opcode('invokeKnownHelper', params.length, name);
|
||||||
} else if (this.options.knownHelpersOnly) {
|
} else if (this.options.knownHelpersOnly) {
|
||||||
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
|
throw new Exception(
|
||||||
|
'You specified knownHelpersOnly, but used the unknown helper ' + name,
|
||||||
|
sexpr
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
path.strict = true;
|
path.strict = true;
|
||||||
path.falsy = true;
|
path.falsy = true;
|
||||||
|
|
||||||
this.accept(path);
|
this.accept(path);
|
||||||
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
|
this.opcode(
|
||||||
|
'invokeHelper',
|
||||||
|
params.length,
|
||||||
|
path.original,
|
||||||
|
AST.helpers.simpleId(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -285,8 +292,8 @@ Compiler.prototype = {
|
|||||||
this.opcode('getContext', path.depth);
|
this.opcode('getContext', path.depth);
|
||||||
|
|
||||||
let name = path.parts[0],
|
let name = path.parts[0],
|
||||||
scoped = AST.helpers.scopedId(path),
|
scoped = AST.helpers.scopedId(path),
|
||||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||||
|
|
||||||
if (blockParamId) {
|
if (blockParamId) {
|
||||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
||||||
@@ -297,7 +304,13 @@ Compiler.prototype = {
|
|||||||
this.options.data = true;
|
this.options.data = true;
|
||||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
||||||
} else {
|
} else {
|
||||||
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
|
this.opcode(
|
||||||
|
'lookupOnContext',
|
||||||
|
path.parts,
|
||||||
|
path.falsy,
|
||||||
|
path.strict,
|
||||||
|
scoped
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -323,8 +336,8 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
Hash: function(hash) {
|
Hash: function(hash) {
|
||||||
let pairs = hash.pairs,
|
let pairs = hash.pairs,
|
||||||
i = 0,
|
i = 0,
|
||||||
l = pairs.length;
|
l = pairs.length;
|
||||||
|
|
||||||
this.opcode('pushHash');
|
this.opcode('pushHash');
|
||||||
|
|
||||||
@@ -339,7 +352,11 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
opcode: function(name) {
|
opcode: function(name) {
|
||||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
|
this.opcodes.push({
|
||||||
|
opcode: name,
|
||||||
|
args: slice.call(arguments, 1),
|
||||||
|
loc: this.sourceNode[0].loc
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addDepth: function(depth) {
|
addDepth: function(depth) {
|
||||||
@@ -368,8 +385,7 @@ Compiler.prototype = {
|
|||||||
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
||||||
if (isEligible && !isHelper) {
|
if (isEligible && !isHelper) {
|
||||||
let name = sexpr.path.parts[0],
|
let name = sexpr.path.parts[0],
|
||||||
options = this.options;
|
options = this.options;
|
||||||
|
|
||||||
if (options.knownHelpers[name]) {
|
if (options.knownHelpers[name]) {
|
||||||
isHelper = true;
|
isHelper = true;
|
||||||
} else if (options.knownHelpersOnly) {
|
} else if (options.knownHelpersOnly) {
|
||||||
@@ -397,9 +413,7 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
if (this.stringParams) {
|
if (this.stringParams) {
|
||||||
if (value.replace) {
|
if (value.replace) {
|
||||||
value = value
|
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||||
.replace(/^(\.?\.\/)*/g, '')
|
|
||||||
.replace(/\//g, '.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.depth) {
|
if (val.depth) {
|
||||||
@@ -417,7 +431,7 @@ Compiler.prototype = {
|
|||||||
if (this.trackIds) {
|
if (this.trackIds) {
|
||||||
let blockParamIndex;
|
let blockParamIndex;
|
||||||
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
||||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||||
}
|
}
|
||||||
if (blockParamIndex) {
|
if (blockParamIndex) {
|
||||||
let blockParamChild = val.parts.slice(1).join('.');
|
let blockParamChild = val.parts.slice(1).join('.');
|
||||||
@@ -426,9 +440,9 @@ Compiler.prototype = {
|
|||||||
value = val.original || value;
|
value = val.original || value;
|
||||||
if (value.replace) {
|
if (value.replace) {
|
||||||
value = value
|
value = value
|
||||||
.replace(/^this(?:\.|$)/, '')
|
.replace(/^this(?:\.|$)/, '')
|
||||||
.replace(/^\.\//, '')
|
.replace(/^\.\//, '')
|
||||||
.replace(/^\.$/, '');
|
.replace(/^\.$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.opcode('pushId', val.type, value);
|
this.opcode('pushId', val.type, value);
|
||||||
@@ -455,9 +469,13 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
blockParamIndex: function(name) {
|
blockParamIndex: function(name) {
|
||||||
for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
|
for (
|
||||||
|
let depth = 0, len = this.options.blockParams.length;
|
||||||
|
depth < len;
|
||||||
|
depth++
|
||||||
|
) {
|
||||||
let blockParams = this.options.blockParams[depth],
|
let blockParams = this.options.blockParams[depth],
|
||||||
param = blockParams && indexOf(blockParams, name);
|
param = blockParams && indexOf(blockParams, name);
|
||||||
if (blockParams && param >= 0) {
|
if (blockParams && param >= 0) {
|
||||||
return [depth, param];
|
return [depth, param];
|
||||||
}
|
}
|
||||||
@@ -466,8 +484,14 @@ Compiler.prototype = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function precompile(input, options, env) {
|
export function precompile(input, options, env) {
|
||||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
if (
|
||||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
|
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 || {};
|
options = options || {};
|
||||||
@@ -479,13 +503,19 @@ export function precompile(input, options, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ast = env.parse(input, options),
|
let ast = env.parse(input, options),
|
||||||
environment = new env.Compiler().compile(ast, options);
|
environment = new env.Compiler().compile(ast, options);
|
||||||
return new env.JavaScriptCompiler().compile(environment, options);
|
return new env.JavaScriptCompiler().compile(environment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(input, options = {}, env) {
|
export function compile(input, options = {}, env) {
|
||||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
if (
|
||||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
|
input == null ||
|
||||||
|
(typeof input !== 'string' && input.type !== 'Program')
|
||||||
|
) {
|
||||||
|
throw new Exception(
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||||
|
input
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options = extend({}, options);
|
options = extend({}, options);
|
||||||
@@ -500,8 +530,13 @@ export function compile(input, options = {}, env) {
|
|||||||
|
|
||||||
function compileInput() {
|
function compileInput() {
|
||||||
let ast = env.parse(input, options),
|
let ast = env.parse(input, options),
|
||||||
environment = new env.Compiler().compile(ast, options),
|
environment = new env.Compiler().compile(ast, options),
|
||||||
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
templateSpec = new env.JavaScriptCompiler().compile(
|
||||||
|
environment,
|
||||||
|
options,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
return env.template(templateSpec);
|
return env.template(templateSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ function validateClose(open, close) {
|
|||||||
close = close.path ? close.path.original : close;
|
close = close.path ? close.path.original : close;
|
||||||
|
|
||||||
if (open.path.original !== close) {
|
if (open.path.original !== close) {
|
||||||
let errorNode = {loc: open.path.loc};
|
let errorNode = { loc: open.path.loc };
|
||||||
|
|
||||||
throw new Exception(open.path.original + " doesn't match " + close, errorNode);
|
throw new Exception(
|
||||||
|
open.path.original + " doesn't match " + close,
|
||||||
|
errorNode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +27,7 @@ export function SourceLocation(source, locInfo) {
|
|||||||
|
|
||||||
export function id(token) {
|
export function id(token) {
|
||||||
if (/^\[.*\]$/.test(token)) {
|
if (/^\[.*\]$/.test(token)) {
|
||||||
return token.substr(1, token.length - 2);
|
return token.substring(1, token.length - 1);
|
||||||
} else {
|
} else {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -38,31 +41,28 @@ export function stripFlags(open, close) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function stripComment(comment) {
|
export function stripComment(comment) {
|
||||||
return comment.replace(/^\{\{~?\!-?-?/, '')
|
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
|
||||||
.replace(/-?-?~?\}\}$/, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function preparePath(data, parts, loc) {
|
export function preparePath(data, parts, loc) {
|
||||||
loc = this.locInfo(loc);
|
loc = this.locInfo(loc);
|
||||||
|
|
||||||
let original = data ? '@' : '',
|
let original = data ? '@' : '',
|
||||||
dig = [],
|
dig = [],
|
||||||
depth = 0,
|
depth = 0;
|
||||||
depthString = '';
|
|
||||||
|
|
||||||
for (let i = 0, l = parts.length; i < l; i++) {
|
for (let i = 0, l = parts.length; i < l; i++) {
|
||||||
let part = parts[i].part,
|
let part = parts[i].part,
|
||||||
// If we have [] syntax then we do not treat path references as operators,
|
// If we have [] syntax then we do not treat path references as operators,
|
||||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||||
isLiteral = parts[i].original !== part;
|
isLiteral = parts[i].original !== part;
|
||||||
original += (parts[i].separator || '') + part;
|
original += (parts[i].separator || '') + part;
|
||||||
|
|
||||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||||
if (dig.length > 0) {
|
if (dig.length > 0) {
|
||||||
throw new Exception('Invalid path: ' + original, {loc});
|
throw new Exception('Invalid path: ' + original, { loc });
|
||||||
} else if (part === '..') {
|
} else if (part === '..') {
|
||||||
depth++;
|
depth++;
|
||||||
depthString += '../';
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dig.push(part);
|
dig.push(part);
|
||||||
@@ -82,9 +82,9 @@ export function preparePath(data, parts, loc) {
|
|||||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||||
// Must use charAt to support IE pre-10
|
// Must use charAt to support IE pre-10
|
||||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||||
|
|
||||||
let decorator = (/\*/.test(open));
|
let decorator = /\*/.test(open);
|
||||||
return {
|
return {
|
||||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||||
path,
|
path,
|
||||||
@@ -120,21 +120,30 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
|
export function prepareBlock(
|
||||||
|
openBlock,
|
||||||
|
program,
|
||||||
|
inverseAndProgram,
|
||||||
|
close,
|
||||||
|
inverted,
|
||||||
|
locInfo
|
||||||
|
) {
|
||||||
if (close && close.path) {
|
if (close && close.path) {
|
||||||
validateClose(openBlock, close);
|
validateClose(openBlock, close);
|
||||||
}
|
}
|
||||||
|
|
||||||
let decorator = (/\*/.test(openBlock.open));
|
let decorator = /\*/.test(openBlock.open);
|
||||||
|
|
||||||
program.blockParams = openBlock.blockParams;
|
program.blockParams = openBlock.blockParams;
|
||||||
|
|
||||||
let inverse,
|
let inverse, inverseStrip;
|
||||||
inverseStrip;
|
|
||||||
|
|
||||||
if (inverseAndProgram) {
|
if (inverseAndProgram) {
|
||||||
if (decorator) {
|
if (decorator) {
|
||||||
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
|
throw new Exception(
|
||||||
|
'Unexpected inverse block on decorator',
|
||||||
|
inverseAndProgram
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inverseAndProgram.chain) {
|
if (inverseAndProgram.chain) {
|
||||||
@@ -168,7 +177,7 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
|
|||||||
export function prepareProgram(statements, loc) {
|
export function prepareProgram(statements, loc) {
|
||||||
if (!loc && statements.length) {
|
if (!loc && statements.length) {
|
||||||
const firstLoc = statements[0].loc,
|
const firstLoc = statements[0].loc,
|
||||||
lastLoc = statements[statements.length - 1].loc;
|
lastLoc = statements[statements.length - 1].loc;
|
||||||
|
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (firstLoc && lastLoc) {
|
if (firstLoc && lastLoc) {
|
||||||
@@ -194,7 +203,6 @@ export function prepareProgram(statements, loc) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function preparePartialBlock(open, program, close, locInfo) {
|
export function preparePartialBlock(open, program, close, locInfo) {
|
||||||
validateClose(open, close);
|
validateClose(open, close);
|
||||||
|
|
||||||
@@ -209,4 +217,3 @@ export function preparePartialBlock(open, program, close, locInfo) {
|
|||||||
loc: this.locInfo(locInfo)
|
loc: this.locInfo(locInfo)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
import {isArray} from '../utils';
|
import { isArray } from '../utils';
|
||||||
import CodeGen from './code-gen';
|
import CodeGen from './code-gen';
|
||||||
|
|
||||||
function Literal(value) {
|
function Literal(value) {
|
||||||
@@ -12,20 +12,21 @@ function JavaScriptCompiler() {}
|
|||||||
JavaScriptCompiler.prototype = {
|
JavaScriptCompiler.prototype = {
|
||||||
// PUBLIC API: You can override these methods in a subclass to provide
|
// PUBLIC API: You can override these methods in a subclass to provide
|
||||||
// alternative compiled forms for name lookup and buffering semantics
|
// alternative compiled forms for name lookup and buffering semantics
|
||||||
nameLookup: function(parent, name/* , type*/) {
|
nameLookup: function(parent, name /*, type */) {
|
||||||
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
return this.internalNameLookup(parent, name);
|
||||||
return [parent, '.', name];
|
|
||||||
} else {
|
|
||||||
return [parent, '[', JSON.stringify(name), ']'];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
depthedLookup: function(name) {
|
depthedLookup: function(name) {
|
||||||
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
|
return [
|
||||||
|
this.aliasable('container.lookup'),
|
||||||
|
'(depths, ',
|
||||||
|
JSON.stringify(name),
|
||||||
|
')'
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
compilerInfo: function() {
|
compilerInfo: function() {
|
||||||
const revision = COMPILER_REVISION,
|
const revision = COMPILER_REVISION,
|
||||||
versions = REVISION_CHANGES[revision];
|
versions = REVISION_CHANGES[revision];
|
||||||
return [revision, versions];
|
return [revision, versions];
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -53,6 +54,12 @@ JavaScriptCompiler.prototype = {
|
|||||||
return this.quotedString('');
|
return this.quotedString('');
|
||||||
},
|
},
|
||||||
// END PUBLIC API
|
// END PUBLIC API
|
||||||
|
internalNameLookup: function(parent, name) {
|
||||||
|
this.lookupPropertyFunctionIsUsed = true;
|
||||||
|
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
|
||||||
|
},
|
||||||
|
|
||||||
|
lookupPropertyFunctionIsUsed: false,
|
||||||
|
|
||||||
compile: function(environment, options, context, asObject) {
|
compile: function(environment, options, context, asObject) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
@@ -82,14 +89,18 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
this.compileChildren(environment, options);
|
this.compileChildren(environment, options);
|
||||||
|
|
||||||
this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
|
this.useDepths =
|
||||||
|
this.useDepths ||
|
||||||
|
environment.useDepths ||
|
||||||
|
environment.useDecorators ||
|
||||||
|
this.options.compat;
|
||||||
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
|
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
|
||||||
|
|
||||||
let opcodes = environment.opcodes,
|
let opcodes = environment.opcodes,
|
||||||
opcode,
|
opcode,
|
||||||
firstLoc,
|
firstLoc,
|
||||||
i,
|
i,
|
||||||
l;
|
l;
|
||||||
|
|
||||||
for (i = 0, l = opcodes.length; i < l; i++) {
|
for (i = 0, l = opcodes.length; i < l; i++) {
|
||||||
opcode = opcodes[i];
|
opcode = opcodes[i];
|
||||||
@@ -111,13 +122,28 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (!this.decorators.isEmpty()) {
|
if (!this.decorators.isEmpty()) {
|
||||||
this.useDecorators = true;
|
this.useDecorators = true;
|
||||||
|
|
||||||
this.decorators.prepend('var decorators = container.decorators;\n');
|
this.decorators.prepend([
|
||||||
|
'var decorators = container.decorators, ',
|
||||||
|
this.lookupPropertyFunctionVarDeclaration(),
|
||||||
|
';\n'
|
||||||
|
]);
|
||||||
this.decorators.push('return fn;');
|
this.decorators.push('return fn;');
|
||||||
|
|
||||||
if (asObject) {
|
if (asObject) {
|
||||||
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
|
this.decorators = Function.apply(this, [
|
||||||
|
'fn',
|
||||||
|
'props',
|
||||||
|
'container',
|
||||||
|
'depth0',
|
||||||
|
'data',
|
||||||
|
'blockParams',
|
||||||
|
'depths',
|
||||||
|
this.decorators.merge()
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
|
this.decorators.prepend(
|
||||||
|
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
|
||||||
|
);
|
||||||
this.decorators.push('}\n');
|
this.decorators.push('}\n');
|
||||||
this.decorators = this.decorators.merge();
|
this.decorators = this.decorators.merge();
|
||||||
}
|
}
|
||||||
@@ -133,11 +159,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.decorators) {
|
if (this.decorators) {
|
||||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
||||||
ret.useDecorators = true;
|
ret.useDecorators = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {programs, decorators} = this.context;
|
let { programs, decorators } = this.context;
|
||||||
for (i = 0, l = programs.length; i < l; i++) {
|
for (i = 0, l = programs.length; i < l; i++) {
|
||||||
if (programs[i]) {
|
if (programs[i]) {
|
||||||
ret[i] = programs[i];
|
ret[i] = programs[i];
|
||||||
@@ -167,11 +193,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (!asObject) {
|
if (!asObject) {
|
||||||
ret.compiler = JSON.stringify(ret.compiler);
|
ret.compiler = JSON.stringify(ret.compiler);
|
||||||
|
|
||||||
this.source.currentLocation = {start: {line: 1, column: 0}};
|
this.source.currentLocation = { start: { line: 1, column: 0 } };
|
||||||
ret = this.objectLiteral(ret);
|
ret = this.objectLiteral(ret);
|
||||||
|
|
||||||
if (options.srcName) {
|
if (options.srcName) {
|
||||||
ret = ret.toStringWithSourceMap({file: options.destName});
|
ret = ret.toStringWithSourceMap({ file: options.destName });
|
||||||
ret.map = ret.map && ret.map.toString();
|
ret.map = ret.map && ret.map.toString();
|
||||||
} else {
|
} else {
|
||||||
ret = ret.toString();
|
ret = ret.toString();
|
||||||
@@ -209,13 +235,16 @@ JavaScriptCompiler.prototype = {
|
|||||||
// aliases will not be used, but this case is already being run on the client and
|
// aliases will not be used, but this case is already being run on the client and
|
||||||
// we aren't concern about minimizing the template size.
|
// we aren't concern about minimizing the template size.
|
||||||
let aliasCount = 0;
|
let aliasCount = 0;
|
||||||
for (let alias in this.aliases) { // eslint-disable-line guard-for-in
|
Object.keys(this.aliases).forEach(alias => {
|
||||||
let node = this.aliases[alias];
|
let node = this.aliases[alias];
|
||||||
|
if (node.children && node.referenceCount > 1) {
|
||||||
if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
|
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
|
||||||
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
|
|
||||||
node.children[0] = 'alias' + aliasCount;
|
node.children[0] = 'alias' + aliasCount;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.lookupPropertyFunctionIsUsed) {
|
||||||
|
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
||||||
@@ -235,18 +264,23 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
return Function.apply(this, params);
|
return Function.apply(this, params);
|
||||||
} else {
|
} else {
|
||||||
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
|
return this.source.wrap([
|
||||||
|
'function(',
|
||||||
|
params.join(','),
|
||||||
|
') {\n ',
|
||||||
|
source,
|
||||||
|
'}'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mergeSource: function(varDeclarations) {
|
mergeSource: function(varDeclarations) {
|
||||||
let isSimple = this.environment.isSimple,
|
let isSimple = this.environment.isSimple,
|
||||||
appendOnly = !this.forceBuffer,
|
appendOnly = !this.forceBuffer,
|
||||||
appendFirst,
|
appendFirst,
|
||||||
|
sourceSeen,
|
||||||
sourceSeen,
|
bufferStart,
|
||||||
bufferStart,
|
bufferEnd;
|
||||||
bufferEnd;
|
this.source.each(line => {
|
||||||
this.source.each((line) => {
|
|
||||||
if (line.appendToBuffer) {
|
if (line.appendToBuffer) {
|
||||||
if (bufferStart) {
|
if (bufferStart) {
|
||||||
line.prepend(' + ');
|
line.prepend(' + ');
|
||||||
@@ -272,16 +306,16 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (appendOnly) {
|
if (appendOnly) {
|
||||||
if (bufferStart) {
|
if (bufferStart) {
|
||||||
bufferStart.prepend('return ');
|
bufferStart.prepend('return ""+');
|
||||||
bufferEnd.add(';');
|
bufferEnd.add(';');
|
||||||
} else if (!sourceSeen) {
|
} else if (!sourceSeen) {
|
||||||
this.source.push('return "";');
|
this.source.push('return "";');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
|
varDeclarations +=
|
||||||
|
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
|
||||||
|
|
||||||
if (bufferStart) {
|
if (bufferStart) {
|
||||||
bufferStart.prepend('return buffer + ');
|
bufferStart.prepend('return buffer + ');
|
||||||
@@ -292,12 +326,25 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (varDeclarations) {
|
if (varDeclarations) {
|
||||||
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
|
this.source.prepend(
|
||||||
|
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.source.merge();
|
return this.source.merge();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
lookupPropertyFunctionVarDeclaration: function() {
|
||||||
|
return `
|
||||||
|
lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
},
|
||||||
|
|
||||||
// [blockValue]
|
// [blockValue]
|
||||||
//
|
//
|
||||||
// On stack, before: hash, inverse, program, value
|
// On stack, before: hash, inverse, program, value
|
||||||
@@ -308,8 +355,10 @@ JavaScriptCompiler.prototype = {
|
|||||||
// replace it on the stack with the result of properly
|
// replace it on the stack with the result of properly
|
||||||
// invoking blockHelperMissing.
|
// invoking blockHelperMissing.
|
||||||
blockValue: function(name) {
|
blockValue: function(name) {
|
||||||
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
|
let blockHelperMissing = this.aliasable(
|
||||||
params = [this.contextName(0)];
|
'container.hooks.blockHelperMissing'
|
||||||
|
),
|
||||||
|
params = [this.contextName(0)];
|
||||||
this.setupHelperArgs(name, 0, params);
|
this.setupHelperArgs(name, 0, params);
|
||||||
|
|
||||||
let blockName = this.popStack();
|
let blockName = this.popStack();
|
||||||
@@ -326,8 +375,10 @@ JavaScriptCompiler.prototype = {
|
|||||||
// On stack, after, if lastHelper: value
|
// On stack, after, if lastHelper: value
|
||||||
ambiguousBlockValue: function() {
|
ambiguousBlockValue: function() {
|
||||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||||
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
|
let blockHelperMissing = this.aliasable(
|
||||||
params = [this.contextName(0)];
|
'container.hooks.blockHelperMissing'
|
||||||
|
),
|
||||||
|
params = [this.contextName(0)];
|
||||||
this.setupHelperArgs('', 0, params, true);
|
this.setupHelperArgs('', 0, params, true);
|
||||||
|
|
||||||
this.flushInline();
|
this.flushInline();
|
||||||
@@ -336,9 +387,14 @@ JavaScriptCompiler.prototype = {
|
|||||||
params.splice(1, 0, current);
|
params.splice(1, 0, current);
|
||||||
|
|
||||||
this.pushSource([
|
this.pushSource([
|
||||||
'if (!', this.lastHelper, ') { ',
|
'if (!',
|
||||||
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
|
this.lastHelper,
|
||||||
'}']);
|
') { ',
|
||||||
|
current,
|
||||||
|
' = ',
|
||||||
|
this.source.functionCall(blockHelperMissing, 'call', params),
|
||||||
|
'}'
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// [appendContent]
|
// [appendContent]
|
||||||
@@ -368,14 +424,24 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Otherwise, the empty string is appended
|
// Otherwise, the empty string is appended
|
||||||
append: function() {
|
append: function() {
|
||||||
if (this.isInline()) {
|
if (this.isInline()) {
|
||||||
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
|
this.replaceStack(current => [' != null ? ', current, ' : ""']);
|
||||||
|
|
||||||
this.pushSource(this.appendToBuffer(this.popStack()));
|
this.pushSource(this.appendToBuffer(this.popStack()));
|
||||||
} else {
|
} else {
|
||||||
let local = this.popStack();
|
let local = this.popStack();
|
||||||
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
|
this.pushSource([
|
||||||
|
'if (',
|
||||||
|
local,
|
||||||
|
' != null) { ',
|
||||||
|
this.appendToBuffer(local, undefined, true),
|
||||||
|
' }'
|
||||||
|
]);
|
||||||
if (this.environment.isSimple) {
|
if (this.environment.isSimple) {
|
||||||
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
|
this.pushSource([
|
||||||
|
'else { ',
|
||||||
|
this.appendToBuffer("''", undefined, true),
|
||||||
|
' }'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -387,8 +453,14 @@ JavaScriptCompiler.prototype = {
|
|||||||
//
|
//
|
||||||
// Escape `value` and append it to the buffer
|
// Escape `value` and append it to the buffer
|
||||||
appendEscaped: function() {
|
appendEscaped: function() {
|
||||||
this.pushSource(this.appendToBuffer(
|
this.pushSource(
|
||||||
[this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
|
this.appendToBuffer([
|
||||||
|
this.aliasable('container.escapeExpression'),
|
||||||
|
'(',
|
||||||
|
this.popStack(),
|
||||||
|
')'
|
||||||
|
])
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// [getContext]
|
// [getContext]
|
||||||
@@ -465,14 +537,16 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
resolvePath: function(type, parts, i, falsy, strict) {
|
resolvePath: function(type, parts, i, falsy, strict) {
|
||||||
if (this.options.strict || this.options.assumeObjects) {
|
if (this.options.strict || this.options.assumeObjects) {
|
||||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
this.push(
|
||||||
|
strictLookup(this.options.strict && strict, this, parts, i, type)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = parts.length;
|
let len = parts.length;
|
||||||
for (; i < len; i++) {
|
for (; i < len; i++) {
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
this.replaceStack((current) => {
|
this.replaceStack(current => {
|
||||||
let lookup = this.nameLookup(current, parts[i], type);
|
let lookup = this.nameLookup(current, parts[i], type);
|
||||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||||
// needs to have the special handling for these values.
|
// needs to have the special handling for these values.
|
||||||
@@ -495,7 +569,14 @@ JavaScriptCompiler.prototype = {
|
|||||||
// If the `value` is a lambda, replace it on the stack by
|
// If the `value` is a lambda, replace it on the stack by
|
||||||
// the return value of the lambda
|
// the return value of the lambda
|
||||||
resolvePossibleLambda: function() {
|
resolvePossibleLambda: function() {
|
||||||
this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
|
this.push([
|
||||||
|
this.aliasable('container.lambda'),
|
||||||
|
'(',
|
||||||
|
this.popStack(),
|
||||||
|
', ',
|
||||||
|
this.contextName(0),
|
||||||
|
')'
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// [pushStringParam]
|
// [pushStringParam]
|
||||||
@@ -535,7 +616,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (this.hash) {
|
if (this.hash) {
|
||||||
this.hashes.push(this.hash);
|
this.hashes.push(this.hash);
|
||||||
}
|
}
|
||||||
this.hash = {values: [], types: [], contexts: [], ids: []};
|
this.hash = { values: {}, types: [], contexts: [], ids: [] };
|
||||||
},
|
},
|
||||||
popHash: function() {
|
popHash: function() {
|
||||||
let hash = this.hash;
|
let hash = this.hash;
|
||||||
@@ -599,11 +680,16 @@ JavaScriptCompiler.prototype = {
|
|||||||
// and inserts the decorator into the decorators list.
|
// and inserts the decorator into the decorators list.
|
||||||
registerDecorator(paramSize, name) {
|
registerDecorator(paramSize, name) {
|
||||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||||
options = this.setupHelperArgs(name, paramSize);
|
options = this.setupHelperArgs(name, paramSize);
|
||||||
|
|
||||||
this.decorators.push([
|
this.decorators.push([
|
||||||
'fn = ',
|
'fn = ',
|
||||||
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
|
this.decorators.functionCall(foundDecorator, '', [
|
||||||
|
'fn',
|
||||||
|
'props',
|
||||||
|
'container',
|
||||||
|
options
|
||||||
|
]),
|
||||||
' || fn;'
|
' || fn;'
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
@@ -619,18 +705,43 @@ JavaScriptCompiler.prototype = {
|
|||||||
// If the helper is not found, `helperMissing` is called.
|
// If the helper is not found, `helperMissing` is called.
|
||||||
invokeHelper: function(paramSize, name, isSimple) {
|
invokeHelper: function(paramSize, name, isSimple) {
|
||||||
let nonHelper = this.popStack(),
|
let nonHelper = this.popStack(),
|
||||||
helper = this.setupHelper(paramSize, name),
|
helper = this.setupHelper(paramSize, name);
|
||||||
simple = isSimple ? [helper.name, ' || '] : '';
|
|
||||||
|
|
||||||
let lookup = ['('].concat(simple, nonHelper);
|
let possibleFunctionCalls = [];
|
||||||
if (!this.options.strict) {
|
|
||||||
lookup.push(' || ', this.aliasable('helpers.helperMissing'));
|
if (isSimple) {
|
||||||
|
// direct call to helper
|
||||||
|
possibleFunctionCalls.push(helper.name);
|
||||||
|
}
|
||||||
|
// call a function from the input object
|
||||||
|
possibleFunctionCalls.push(nonHelper);
|
||||||
|
if (!this.options.strict) {
|
||||||
|
possibleFunctionCalls.push(
|
||||||
|
this.aliasable('container.hooks.helperMissing')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
lookup.push(')');
|
|
||||||
|
|
||||||
this.push(this.source.functionCall(lookup, 'call', helper.callParams));
|
let functionLookupCode = [
|
||||||
|
'(',
|
||||||
|
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
|
||||||
|
')'
|
||||||
|
];
|
||||||
|
let functionCall = this.source.functionCall(
|
||||||
|
functionLookupCode,
|
||||||
|
'call',
|
||||||
|
helper.callParams
|
||||||
|
);
|
||||||
|
this.push(functionCall);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
itemsSeparatedBy: function(items, separator) {
|
||||||
|
let result = [];
|
||||||
|
result.push(items[0]);
|
||||||
|
for (let i = 1; i < items.length; i++) {
|
||||||
|
result.push(separator, items[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
// [invokeKnownHelper]
|
// [invokeKnownHelper]
|
||||||
//
|
//
|
||||||
// On stack, before: hash, inverse, program, params..., ...
|
// On stack, before: hash, inverse, program, params..., ...
|
||||||
@@ -663,22 +774,31 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.emptyHash();
|
this.emptyHash();
|
||||||
let helper = this.setupHelper(0, name, helperCall);
|
let helper = this.setupHelper(0, name, helperCall);
|
||||||
|
|
||||||
let helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
let helperName = (this.lastHelper = this.nameLookup(
|
||||||
|
'helpers',
|
||||||
|
name,
|
||||||
|
'helper'
|
||||||
|
));
|
||||||
|
|
||||||
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
|
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
|
||||||
if (!this.options.strict) {
|
if (!this.options.strict) {
|
||||||
lookup[0] = '(helper = ';
|
lookup[0] = '(helper = ';
|
||||||
lookup.push(
|
lookup.push(
|
||||||
' != null ? helper : ',
|
' != null ? helper : ',
|
||||||
this.aliasable('helpers.helperMissing')
|
this.aliasable('container.hooks.helperMissing')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.push([
|
this.push([
|
||||||
'(', lookup,
|
'(',
|
||||||
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
|
lookup,
|
||||||
'(typeof helper === ', this.aliasable('"function"'), ' ? ',
|
helper.paramsInit ? ['),(', helper.paramsInit] : [],
|
||||||
this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
|
'),',
|
||||||
|
'(typeof helper === ',
|
||||||
|
this.aliasable('"function"'),
|
||||||
|
' ? ',
|
||||||
|
this.source.functionCall('helper', 'call', helper.callParams),
|
||||||
|
' : helper))'
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -691,7 +811,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
// and pushes the result of the invocation back.
|
// and pushes the result of the invocation back.
|
||||||
invokePartial: function(isDynamic, name, indent) {
|
invokePartial: function(isDynamic, name, indent) {
|
||||||
let params = [],
|
let params = [],
|
||||||
options = this.setupParams(name, 1, params);
|
options = this.setupParams(name, 1, params);
|
||||||
|
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
name = this.popStack();
|
name = this.popStack();
|
||||||
@@ -728,9 +848,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
// Pops a value off the stack and assigns it to the current hash
|
// Pops a value off the stack and assigns it to the current hash
|
||||||
assignToHash: function(key) {
|
assignToHash: function(key) {
|
||||||
let value = this.popStack(),
|
let value = this.popStack(),
|
||||||
context,
|
context,
|
||||||
type,
|
type,
|
||||||
id;
|
id;
|
||||||
|
|
||||||
if (this.trackIds) {
|
if (this.trackIds) {
|
||||||
id = this.popStack();
|
id = this.popStack();
|
||||||
@@ -756,8 +876,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
pushId: function(type, name, child) {
|
pushId: function(type, name, child) {
|
||||||
if (type === 'BlockParam') {
|
if (type === 'BlockParam') {
|
||||||
this.pushStackLiteral(
|
this.pushStackLiteral(
|
||||||
'blockParams[' + name[0] + '].path[' + name[1] + ']'
|
'blockParams[' +
|
||||||
+ (child ? ' + ' + JSON.stringify('.' + child) : ''));
|
name[0] +
|
||||||
|
'].path[' +
|
||||||
|
name[1] +
|
||||||
|
']' +
|
||||||
|
(child ? ' + ' + JSON.stringify('.' + child) : '')
|
||||||
|
);
|
||||||
} else if (type === 'PathExpression') {
|
} else if (type === 'PathExpression') {
|
||||||
this.pushString(name);
|
this.pushString(name);
|
||||||
} else if (type === 'SubExpression') {
|
} else if (type === 'SubExpression') {
|
||||||
@@ -772,20 +897,27 @@ JavaScriptCompiler.prototype = {
|
|||||||
compiler: JavaScriptCompiler,
|
compiler: JavaScriptCompiler,
|
||||||
|
|
||||||
compileChildren: function(environment, options) {
|
compileChildren: function(environment, options) {
|
||||||
let children = environment.children, child, compiler;
|
let children = environment.children,
|
||||||
|
child,
|
||||||
|
compiler;
|
||||||
|
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
child = children[i];
|
child = children[i];
|
||||||
compiler = new this.compiler(); // eslint-disable-line new-cap
|
compiler = new this.compiler(); // eslint-disable-line new-cap
|
||||||
|
|
||||||
let existing = this.matchExistingProgram(child);
|
let existing = this.matchExistingProgram(child);
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||||
let index = this.context.programs.length;
|
let index = this.context.programs.length;
|
||||||
child.index = index;
|
child.index = index;
|
||||||
child.name = 'program' + index;
|
child.name = 'program' + index;
|
||||||
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
|
this.context.programs[index] = compiler.compile(
|
||||||
|
child,
|
||||||
|
options,
|
||||||
|
this.context,
|
||||||
|
!this.precompile
|
||||||
|
);
|
||||||
this.context.decorators[index] = compiler.decorators;
|
this.context.decorators[index] = compiler.decorators;
|
||||||
this.context.environments[index] = child;
|
this.context.environments[index] = child;
|
||||||
|
|
||||||
@@ -813,7 +945,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
programExpression: function(guid) {
|
programExpression: function(guid) {
|
||||||
let child = this.environment.children[guid],
|
let child = this.environment.children[guid],
|
||||||
programParams = [child.index, 'data', child.blockParams];
|
programParams = [child.index, 'data', child.blockParams];
|
||||||
|
|
||||||
if (this.useBlockParams || this.useDepths) {
|
if (this.useBlockParams || this.useDepths) {
|
||||||
programParams.push('blockParams');
|
programParams.push('blockParams');
|
||||||
@@ -848,7 +980,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
pushSource: function(source) {
|
pushSource: function(source) {
|
||||||
if (this.pendingContent) {
|
if (this.pendingContent) {
|
||||||
this.source.push(
|
this.source.push(
|
||||||
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
|
this.appendToBuffer(
|
||||||
|
this.source.quotedString(this.pendingContent),
|
||||||
|
this.pendingLocation
|
||||||
|
)
|
||||||
|
);
|
||||||
this.pendingContent = undefined;
|
this.pendingContent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,9 +995,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
replaceStack: function(callback) {
|
replaceStack: function(callback) {
|
||||||
let prefix = ['('],
|
let prefix = ['('],
|
||||||
stack,
|
stack,
|
||||||
createdStack,
|
createdStack,
|
||||||
usedLiteral;
|
usedLiteral;
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!this.isInline()) {
|
if (!this.isInline()) {
|
||||||
@@ -898,7 +1034,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
incrStack: function() {
|
incrStack: function() {
|
||||||
this.stackSlot++;
|
this.stackSlot++;
|
||||||
if (this.stackSlot > this.stackVars.length) { this.stackVars.push('stack' + this.stackSlot); }
|
if (this.stackSlot > this.stackVars.length) {
|
||||||
|
this.stackVars.push('stack' + this.stackSlot);
|
||||||
|
}
|
||||||
return this.topStackName();
|
return this.topStackName();
|
||||||
},
|
},
|
||||||
topStackName: function() {
|
topStackName: function() {
|
||||||
@@ -925,9 +1063,9 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
popStack: function(wrapped) {
|
popStack: function(wrapped) {
|
||||||
let inline = this.isInline(),
|
let inline = this.isInline(),
|
||||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||||
|
|
||||||
if (!wrapped && (item instanceof Literal)) {
|
if (!wrapped && item instanceof Literal) {
|
||||||
return item.value;
|
return item.value;
|
||||||
} else {
|
} else {
|
||||||
if (!inline) {
|
if (!inline) {
|
||||||
@@ -942,8 +1080,8 @@ JavaScriptCompiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
topStack: function() {
|
topStack: function() {
|
||||||
let stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
||||||
item = stack[stack.length - 1];
|
item = stack[stack.length - 1];
|
||||||
|
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (item instanceof Literal) {
|
if (item instanceof Literal) {
|
||||||
@@ -985,9 +1123,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
setupHelper: function(paramSize, name, blockHelper) {
|
setupHelper: function(paramSize, name, blockHelper) {
|
||||||
let params = [],
|
let params = [],
|
||||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||||
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
||||||
callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`);
|
callContext = this.aliasable(
|
||||||
|
`${this.contextName(0)} != null ? ${this.contextName(
|
||||||
|
0
|
||||||
|
)} : (container.nullContext || {})`
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params: params,
|
params: params,
|
||||||
@@ -999,11 +1141,11 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
setupParams: function(helper, paramSize, params) {
|
setupParams: function(helper, paramSize, params) {
|
||||||
let options = {},
|
let options = {},
|
||||||
contexts = [],
|
contexts = [],
|
||||||
types = [],
|
types = [],
|
||||||
ids = [],
|
ids = [],
|
||||||
objectArgs = !params,
|
objectArgs = !params,
|
||||||
param;
|
param;
|
||||||
|
|
||||||
if (objectArgs) {
|
if (objectArgs) {
|
||||||
params = [];
|
params = [];
|
||||||
@@ -1021,7 +1163,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inverse = this.popStack(),
|
let inverse = this.popStack(),
|
||||||
program = this.popStack();
|
program = this.popStack();
|
||||||
|
|
||||||
// Avoid setting fn and inverse if neither are set. This allows
|
// Avoid setting fn and inverse if neither are set. This allows
|
||||||
// helpers to do a check for `if (options.fn)`
|
// helpers to do a check for `if (options.fn)`
|
||||||
@@ -1069,6 +1211,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
||||||
let options = this.setupParams(helper, paramSize, params);
|
let options = this.setupParams(helper, paramSize, params);
|
||||||
|
options.loc = JSON.stringify(this.source.currentLocation);
|
||||||
options = this.objectLiteral(options);
|
options = this.objectLiteral(options);
|
||||||
if (useRegister) {
|
if (useRegister) {
|
||||||
this.useRegister('options');
|
this.useRegister('options');
|
||||||
@@ -1083,7 +1226,6 @@ JavaScriptCompiler.prototype = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
const reservedWords = (
|
const reservedWords = (
|
||||||
'break else new var' +
|
'break else new var' +
|
||||||
@@ -1104,21 +1246,26 @@ JavaScriptCompiler.prototype = {
|
|||||||
' null true false'
|
' null true false'
|
||||||
).split(' ');
|
).split(' ');
|
||||||
|
|
||||||
const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {});
|
||||||
|
|
||||||
for (let i = 0, l = reservedWords.length; i < l; i++) {
|
for (let i = 0, l = reservedWords.length; i < l; i++) {
|
||||||
compilerWords[reservedWords[i]] = true;
|
compilerWords[reservedWords[i]] = true;
|
||||||
}
|
}
|
||||||
}());
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated May be removed in the next major version
|
||||||
|
*/
|
||||||
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
||||||
return !JavaScriptCompiler.RESERVED_WORDS[name] && (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/).test(name);
|
return (
|
||||||
|
!JavaScriptCompiler.RESERVED_WORDS[name] &&
|
||||||
|
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
function strictLookup(requireTerminal, compiler, parts, i, type) {
|
||||||
let stack = compiler.popStack(),
|
let stack = compiler.popStack(),
|
||||||
i = 0,
|
len = parts.length;
|
||||||
len = parts.length;
|
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
@@ -1128,7 +1275,16 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
|
return [
|
||||||
|
compiler.aliasable('container.strict'),
|
||||||
|
'(',
|
||||||
|
stack,
|
||||||
|
', ',
|
||||||
|
compiler.quotedString(parts[i]),
|
||||||
|
', ',
|
||||||
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
|
' )'
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ PrintVisitor.prototype.pad = function(string) {
|
|||||||
|
|
||||||
PrintVisitor.prototype.Program = function(program) {
|
PrintVisitor.prototype.Program = function(program) {
|
||||||
let out = '',
|
let out = '',
|
||||||
body = program.body,
|
body = program.body,
|
||||||
i, l;
|
i,
|
||||||
|
l;
|
||||||
|
|
||||||
if (program.blockParams) {
|
if (program.blockParams) {
|
||||||
let blockParams = 'BLOCK PARAMS: [';
|
let blockParams = 'BLOCK PARAMS: [';
|
||||||
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
||||||
blockParams += ' ' + program.blockParams[i];
|
blockParams += ' ' + program.blockParams[i];
|
||||||
}
|
}
|
||||||
blockParams += ' ]';
|
blockParams += ' ]';
|
||||||
out += this.pad(blockParams);
|
out += this.pad(blockParams);
|
||||||
@@ -52,11 +53,14 @@ PrintVisitor.prototype.Decorator = function(mustache) {
|
|||||||
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.BlockStatement =
|
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
|
||||||
PrintVisitor.prototype.DecoratorBlock = function(block) {
|
block
|
||||||
|
) {
|
||||||
let out = '';
|
let out = '';
|
||||||
|
|
||||||
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
|
out += this.pad(
|
||||||
|
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
|
||||||
|
);
|
||||||
this.padding++;
|
this.padding++;
|
||||||
out += this.pad(this.SubExpression(block));
|
out += this.pad(this.SubExpression(block));
|
||||||
if (block.program) {
|
if (block.program) {
|
||||||
@@ -66,12 +70,16 @@ PrintVisitor.prototype.DecoratorBlock = function(block) {
|
|||||||
this.padding--;
|
this.padding--;
|
||||||
}
|
}
|
||||||
if (block.inverse) {
|
if (block.inverse) {
|
||||||
if (block.program) { this.padding++; }
|
if (block.program) {
|
||||||
|
this.padding++;
|
||||||
|
}
|
||||||
out += this.pad('{{^}}');
|
out += this.pad('{{^}}');
|
||||||
this.padding++;
|
this.padding++;
|
||||||
out += this.accept(block.inverse);
|
out += this.accept(block.inverse);
|
||||||
this.padding--;
|
this.padding--;
|
||||||
if (block.program) { this.padding--; }
|
if (block.program) {
|
||||||
|
this.padding--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.padding--;
|
this.padding--;
|
||||||
|
|
||||||
@@ -115,8 +123,8 @@ PrintVisitor.prototype.CommentStatement = function(comment) {
|
|||||||
|
|
||||||
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
||||||
let params = sexpr.params,
|
let params = sexpr.params,
|
||||||
paramStrings = [],
|
paramStrings = [],
|
||||||
hash;
|
hash;
|
||||||
|
|
||||||
for (let i = 0, l = params.length; i < l; i++) {
|
for (let i = 0, l = params.length; i < l; i++) {
|
||||||
paramStrings.push(this.accept(params[i]));
|
paramStrings.push(this.accept(params[i]));
|
||||||
@@ -134,7 +142,6 @@ PrintVisitor.prototype.PathExpression = function(id) {
|
|||||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||||
return '"' + string.value + '"';
|
return '"' + string.value + '"';
|
||||||
};
|
};
|
||||||
@@ -157,7 +164,7 @@ PrintVisitor.prototype.NullLiteral = function() {
|
|||||||
|
|
||||||
PrintVisitor.prototype.Hash = function(hash) {
|
PrintVisitor.prototype.Hash = function(hash) {
|
||||||
let pairs = hash.pairs,
|
let pairs = hash.pairs,
|
||||||
joinedPairs = [];
|
joinedPairs = [];
|
||||||
|
|
||||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||||
joinedPairs.push(this.accept(pairs[i]));
|
joinedPairs.push(this.accept(pairs[i]));
|
||||||
|
|||||||
@@ -15,7 +15,14 @@ Visitor.prototype = {
|
|||||||
// Hacky sanity check: This may have a few false positives for type for the helper
|
// 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.
|
// methods but will generally do the right thing without a lot of overhead.
|
||||||
if (value && !Visitor.prototype[value.type]) {
|
if (value && !Visitor.prototype[value.type]) {
|
||||||
throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
|
throw new Exception(
|
||||||
|
'Unexpected node type "' +
|
||||||
|
value.type +
|
||||||
|
'" found when accepting ' +
|
||||||
|
name +
|
||||||
|
' on ' +
|
||||||
|
node.type
|
||||||
|
);
|
||||||
}
|
}
|
||||||
node[name] = value;
|
node[name] = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ WhitespaceControl.prototype.Program = function(program) {
|
|||||||
let body = program.body;
|
let body = program.body;
|
||||||
for (let i = 0, l = body.length; i < l; i++) {
|
for (let i = 0, l = body.length; i < l; i++) {
|
||||||
let current = body[i],
|
let current = body[i],
|
||||||
strip = this.accept(current);
|
strip = this.accept(current);
|
||||||
|
|
||||||
if (!strip) {
|
if (!strip) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
||||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||||
|
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
inlineStandalone =
|
||||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||||
|
|
||||||
if (strip.close) {
|
if (strip.close) {
|
||||||
omitRight(body, i, true);
|
omitRight(body, i, true);
|
||||||
@@ -41,7 +41,7 @@ WhitespaceControl.prototype.Program = function(program) {
|
|||||||
// If we are on a standalone node, save the indent info for partials
|
// If we are on a standalone node, save the indent info for partials
|
||||||
if (current.type === 'PartialStatement') {
|
if (current.type === 'PartialStatement') {
|
||||||
// Pull out the whitespace from the final line
|
// Pull out the whitespace from the final line
|
||||||
current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
|
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,17 +62,17 @@ WhitespaceControl.prototype.Program = function(program) {
|
|||||||
return program;
|
return program;
|
||||||
};
|
};
|
||||||
|
|
||||||
WhitespaceControl.prototype.BlockStatement =
|
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
|
||||||
WhitespaceControl.prototype.DecoratorBlock =
|
block
|
||||||
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
|
) {
|
||||||
this.accept(block.program);
|
this.accept(block.program);
|
||||||
this.accept(block.inverse);
|
this.accept(block.inverse);
|
||||||
|
|
||||||
// Find the inverse program that is involed with whitespace stripping.
|
// Find the inverse program that is involed with whitespace stripping.
|
||||||
let program = block.program || block.inverse,
|
let program = block.program || block.inverse,
|
||||||
inverse = block.program && block.inverse,
|
inverse = block.program && block.inverse,
|
||||||
firstInverse = inverse,
|
firstInverse = inverse,
|
||||||
lastInverse = inverse;
|
lastInverse = inverse;
|
||||||
|
|
||||||
if (inverse && inverse.chained) {
|
if (inverse && inverse.chained) {
|
||||||
firstInverse = inverse.body[0].program;
|
firstInverse = inverse.body[0].program;
|
||||||
@@ -112,9 +112,11 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find standalone else statments
|
// Find standalone else statments
|
||||||
if (!this.options.ignoreStandalone
|
if (
|
||||||
&& isPrevWhitespace(program.body)
|
!this.options.ignoreStandalone &&
|
||||||
&& isNextWhitespace(firstInverse.body)) {
|
isPrevWhitespace(program.body) &&
|
||||||
|
isNextWhitespace(firstInverse.body)
|
||||||
|
) {
|
||||||
omitLeft(program.body);
|
omitLeft(program.body);
|
||||||
omitRight(firstInverse.body);
|
omitRight(firstInverse.body);
|
||||||
}
|
}
|
||||||
@@ -125,13 +127,15 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
|
|||||||
return strip;
|
return strip;
|
||||||
};
|
};
|
||||||
|
|
||||||
WhitespaceControl.prototype.Decorator =
|
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
|
||||||
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
|
mustache
|
||||||
|
) {
|
||||||
return mustache.strip;
|
return mustache.strip;
|
||||||
};
|
};
|
||||||
|
|
||||||
WhitespaceControl.prototype.PartialStatement =
|
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
|
||||||
WhitespaceControl.prototype.CommentStatement = function(node) {
|
node
|
||||||
|
) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
let strip = node.strip || {};
|
let strip = node.strip || {};
|
||||||
return {
|
return {
|
||||||
@@ -141,7 +145,6 @@ WhitespaceControl.prototype.PartialStatement =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function isPrevWhitespace(body, i, isRoot) {
|
function isPrevWhitespace(body, i, isRoot) {
|
||||||
if (i === undefined) {
|
if (i === undefined) {
|
||||||
i = body.length;
|
i = body.length;
|
||||||
@@ -150,13 +153,15 @@ function isPrevWhitespace(body, i, isRoot) {
|
|||||||
// Nodes that end with newlines are considered whitespace (but are special
|
// Nodes that end with newlines are considered whitespace (but are special
|
||||||
// cased for strip operations)
|
// cased for strip operations)
|
||||||
let prev = body[i - 1],
|
let prev = body[i - 1],
|
||||||
sibling = body[i - 2];
|
sibling = body[i - 2];
|
||||||
if (!prev) {
|
if (!prev) {
|
||||||
return isRoot;
|
return isRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev.type === 'ContentStatement') {
|
if (prev.type === 'ContentStatement') {
|
||||||
return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
|
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
|
||||||
|
prev.original
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function isNextWhitespace(body, i, isRoot) {
|
function isNextWhitespace(body, i, isRoot) {
|
||||||
@@ -165,13 +170,15 @@ function isNextWhitespace(body, i, isRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let next = body[i + 1],
|
let next = body[i + 1],
|
||||||
sibling = body[i + 2];
|
sibling = body[i + 2];
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return isRoot;
|
return isRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.type === 'ContentStatement') {
|
if (next.type === 'ContentStatement') {
|
||||||
return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
|
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
|
||||||
|
next.original
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,12 +191,19 @@ function isNextWhitespace(body, i, isRoot) {
|
|||||||
// content is met.
|
// content is met.
|
||||||
function omitRight(body, i, multiple) {
|
function omitRight(body, i, multiple) {
|
||||||
let current = body[i == null ? 0 : i + 1];
|
let current = body[i == null ? 0 : i + 1];
|
||||||
if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
|
if (
|
||||||
|
!current ||
|
||||||
|
current.type !== 'ContentStatement' ||
|
||||||
|
(!multiple && current.rightStripped)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let original = current.value;
|
let original = current.value;
|
||||||
current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
|
current.value = current.value.replace(
|
||||||
|
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
|
||||||
|
''
|
||||||
|
);
|
||||||
current.rightStripped = current.value !== original;
|
current.rightStripped = current.value !== original;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,13 +216,17 @@ function omitRight(body, i, multiple) {
|
|||||||
// content is met.
|
// content is met.
|
||||||
function omitLeft(body, i, multiple) {
|
function omitLeft(body, i, multiple) {
|
||||||
let current = body[i == null ? body.length - 1 : i - 1];
|
let current = body[i == null ? body.length - 1 : i - 1];
|
||||||
if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
|
if (
|
||||||
|
!current ||
|
||||||
|
current.type !== 'ContentStatement' ||
|
||||||
|
(!multiple && current.leftStripped)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We omit the last node if it's whitespace only and not preceeded by a non-content node.
|
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
||||||
let original = current.value;
|
let original = current.value;
|
||||||
current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
|
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
|
||||||
current.leftStripped = current.value !== original;
|
current.leftStripped = current.value !== original;
|
||||||
return current.leftStripped;
|
return current.leftStripped;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
|
|||||||
export function registerDefaultDecorators(instance) {
|
export function registerDefaultDecorators(instance) {
|
||||||
registerInline(instance);
|
registerInline(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {extend} from '../utils';
|
import { extend } from '../utils';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
|
const errorProps = [
|
||||||
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
'description',
|
||||||
|
'fileName',
|
||||||
|
'lineNumber',
|
||||||
|
'endLineNumber',
|
||||||
|
'message',
|
||||||
|
'name',
|
||||||
|
'number',
|
||||||
|
'stack'
|
||||||
|
];
|
||||||
|
|
||||||
function Exception(message, node) {
|
function Exception(message, node) {
|
||||||
let loc = node && node.loc,
|
let loc = node && node.loc,
|
||||||
line,
|
line,
|
||||||
column;
|
endLineNumber,
|
||||||
|
column,
|
||||||
|
endColumn;
|
||||||
|
|
||||||
if (loc) {
|
if (loc) {
|
||||||
line = loc.start.line;
|
line = loc.start.line;
|
||||||
|
endLineNumber = loc.end.line;
|
||||||
column = loc.start.column;
|
column = loc.start.column;
|
||||||
|
endColumn = loc.end.column;
|
||||||
|
|
||||||
message += ' - ' + line + ':' + column;
|
message += ' - ' + line + ':' + column;
|
||||||
}
|
}
|
||||||
@@ -27,6 +40,7 @@ function Exception(message, node) {
|
|||||||
try {
|
try {
|
||||||
if (loc) {
|
if (loc) {
|
||||||
this.lineNumber = line;
|
this.lineNumber = line;
|
||||||
|
this.endLineNumber = endLineNumber;
|
||||||
|
|
||||||
// Work around issue under safari where we can't directly set the column value
|
// Work around issue under safari where we can't directly set the column value
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
@@ -35,8 +49,13 @@ function Exception(message, node) {
|
|||||||
value: column,
|
value: column,
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(this, 'endColumn', {
|
||||||
|
value: endColumn,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.column = column;
|
this.column = column;
|
||||||
|
this.endColumn = endColumn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (nop) {
|
} catch (nop) {
|
||||||
|
|||||||
@@ -15,3 +15,12 @@ export function registerDefaultHelpers(instance) {
|
|||||||
registerLookup(instance);
|
registerLookup(instance);
|
||||||
registerWith(instance);
|
registerWith(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function moveHelperToHooks(instance, helperName, keepHelper) {
|
||||||
|
if (instance.helpers[helperName]) {
|
||||||
|
instance.hooks[helperName] = instance.helpers[helperName];
|
||||||
|
if (!keepHelper) {
|
||||||
|
delete instance.helpers[helperName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {appendContextPath, createFrame, isArray} from '../utils';
|
import { appendContextPath, createFrame, isArray } from '../utils';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||||
let inverse = options.inverse,
|
let inverse = options.inverse,
|
||||||
fn = options.fn;
|
fn = options.fn;
|
||||||
|
|
||||||
if (context === true) {
|
if (context === true) {
|
||||||
return fn(this);
|
return fn(this);
|
||||||
@@ -22,8 +22,11 @@ export default function(instance) {
|
|||||||
} else {
|
} else {
|
||||||
if (options.data && options.ids) {
|
if (options.data && options.ids) {
|
||||||
let data = createFrame(options.data);
|
let data = createFrame(options.data);
|
||||||
data.contextPath = appendContextPath(options.data.contextPath, options.name);
|
data.contextPath = appendContextPath(
|
||||||
options = {data: data};
|
options.data.contextPath,
|
||||||
|
options.name
|
||||||
|
);
|
||||||
|
options = { data: data };
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(context, options);
|
return fn(context, options);
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
|
import {
|
||||||
|
appendContextPath,
|
||||||
|
blockParams,
|
||||||
|
createFrame,
|
||||||
|
isArray,
|
||||||
|
isFunction
|
||||||
|
} from '../utils';
|
||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
@@ -8,17 +14,20 @@ export default function(instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fn = options.fn,
|
let fn = options.fn,
|
||||||
inverse = options.inverse,
|
inverse = options.inverse,
|
||||||
i = 0,
|
i = 0,
|
||||||
ret = '',
|
ret = '',
|
||||||
data,
|
data,
|
||||||
contextPath;
|
contextPath;
|
||||||
|
|
||||||
if (options.data && options.ids) {
|
if (options.data && options.ids) {
|
||||||
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
contextPath =
|
||||||
|
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFunction(context)) { context = context.call(this); }
|
if (isFunction(context)) {
|
||||||
|
context = context.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.data) {
|
if (options.data) {
|
||||||
data = createFrame(options.data);
|
data = createFrame(options.data);
|
||||||
@@ -36,10 +45,15 @@ export default function(instance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = ret + fn(context[field], {
|
ret =
|
||||||
data: data,
|
ret +
|
||||||
blockParams: blockParams([context[field], field], [contextPath + field, null])
|
fn(context[field], {
|
||||||
});
|
data: data,
|
||||||
|
blockParams: blockParams(
|
||||||
|
[context[field], field],
|
||||||
|
[contextPath + field, null]
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context && typeof context === 'object') {
|
if (context && typeof context === 'object') {
|
||||||
@@ -49,21 +63,29 @@ export default function(instance) {
|
|||||||
execIteration(i, i, i === context.length - 1);
|
execIteration(i, i, i === context.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||||
|
const newContext = [];
|
||||||
|
const iterator = context[Symbol.iterator]();
|
||||||
|
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||||
|
newContext.push(it.value);
|
||||||
|
}
|
||||||
|
context = newContext;
|
||||||
|
for (let j = context.length; i < j; i++) {
|
||||||
|
execIteration(i, i, i === context.length - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let priorKey;
|
let priorKey;
|
||||||
|
|
||||||
for (let key in context) {
|
Object.keys(context).forEach(key => {
|
||||||
if (context.hasOwnProperty(key)) {
|
// We're running the iterations one step out of sync so we can detect
|
||||||
// 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
|
||||||
// the last iteration without have to scan the object twice and create
|
// an itermediate keys array.
|
||||||
// an itermediate keys array.
|
if (priorKey !== undefined) {
|
||||||
if (priorKey !== undefined) {
|
execIteration(priorKey, i - 1);
|
||||||
execIteration(priorKey, i - 1);
|
|
||||||
}
|
|
||||||
priorKey = key;
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
priorKey = key;
|
||||||
|
i++;
|
||||||
|
});
|
||||||
if (priorKey !== undefined) {
|
if (priorKey !== undefined) {
|
||||||
execIteration(priorKey, i - 1, true);
|
execIteration(priorKey, i - 1, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export default function(instance) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
// Someone is actually trying to call something, blow up.
|
// Someone is actually trying to call something, blow up.
|
||||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
throw new Exception(
|
||||||
|
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import {isEmpty, isFunction} from '../utils';
|
import { isEmpty, isFunction } from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('if', function(conditional, options) {
|
instance.registerHelper('if', function(conditional, options) {
|
||||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#if requires exactly one argument');
|
||||||
|
}
|
||||||
|
if (isFunction(conditional)) {
|
||||||
|
conditional = conditional.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||||
@@ -15,6 +21,13 @@ export default function(instance) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
instance.registerHelper('unless', function(conditional, options) {
|
instance.registerHelper('unless', function(conditional, options) {
|
||||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#unless requires exactly one argument');
|
||||||
|
}
|
||||||
|
return instance.helpers['if'].call(this, conditional, {
|
||||||
|
fn: options.inverse,
|
||||||
|
inverse: options.fn,
|
||||||
|
hash: options.hash
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('log', function(/* message, options */) {
|
instance.registerHelper('log', function(/* message, options */) {
|
||||||
let args = [undefined],
|
let args = [undefined],
|
||||||
options = arguments[arguments.length - 1];
|
options = arguments[arguments.length - 1];
|
||||||
for (let i = 0; i < arguments.length - 1; i++) {
|
for (let i = 0; i < arguments.length - 1; i++) {
|
||||||
args.push(arguments[i]);
|
args.push(arguments[i]);
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,6 @@ export default function(instance) {
|
|||||||
}
|
}
|
||||||
args[0] = level;
|
args[0] = level;
|
||||||
|
|
||||||
instance.log(... args);
|
instance.log(...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('lookup', function(obj, field) {
|
instance.registerHelper('lookup', function(obj, field, options) {
|
||||||
return obj && obj[field];
|
if (!obj) {
|
||||||
|
// Note for 5.0: Change to "obj == null" in 5.0
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
return options.lookupProperty(obj, field);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
|
import {
|
||||||
|
appendContextPath,
|
||||||
|
blockParams,
|
||||||
|
createFrame,
|
||||||
|
isEmpty,
|
||||||
|
isFunction
|
||||||
|
} from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('with', function(context, options) {
|
instance.registerHelper('with', function(context, options) {
|
||||||
if (isFunction(context)) { context = context.call(this); }
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#with requires exactly one argument');
|
||||||
|
}
|
||||||
|
if (isFunction(context)) {
|
||||||
|
context = context.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
let fn = options.fn;
|
let fn = options.fn;
|
||||||
|
|
||||||
@@ -10,7 +22,10 @@ export default function(instance) {
|
|||||||
let data = options.data;
|
let data = options.data;
|
||||||
if (options.data && options.ids) {
|
if (options.data && options.ids) {
|
||||||
data = createFrame(options.data);
|
data = createFrame(options.data);
|
||||||
data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]);
|
data.contextPath = appendContextPath(
|
||||||
|
options.data.contextPath,
|
||||||
|
options.ids[0]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(context, {
|
return fn(context, {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { extend } from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
|
||||||
|
* The resulting object can be used with "object[property]" to check if a property exists
|
||||||
|
* @param {...object} sources a varargs parameter of source objects that will be merged
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createNewLookupObject(...sources) {
|
||||||
|
return extend(Object.create(null), ...sources);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { createNewLookupObject } from './create-new-lookup-object';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
|
const loggedProperties = Object.create(null);
|
||||||
|
|
||||||
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
|
let defaultMethodWhiteList = Object.create(null);
|
||||||
|
defaultMethodWhiteList['constructor'] = false;
|
||||||
|
defaultMethodWhiteList['__defineGetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__defineSetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__lookupGetter__'] = false;
|
||||||
|
|
||||||
|
let defaultPropertyWhiteList = Object.create(null);
|
||||||
|
// eslint-disable-next-line no-proto
|
||||||
|
defaultPropertyWhiteList['__proto__'] = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultPropertyWhiteList,
|
||||||
|
runtimeOptions.allowedProtoProperties
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultMethodWhiteList,
|
||||||
|
runtimeOptions.allowedProtoMethods
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultIsAllowed(result, protoAccessControl, propertyName) {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
return checkWhiteList(protoAccessControl.methods, propertyName);
|
||||||
|
} else {
|
||||||
|
return checkWhiteList(protoAccessControl.properties, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWhiteList(protoAccessControlForType, propertyName) {
|
||||||
|
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
|
||||||
|
return protoAccessControlForType.whitelist[propertyName] === true;
|
||||||
|
}
|
||||||
|
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||||
|
return protoAccessControlForType.defaultValue;
|
||||||
|
}
|
||||||
|
logUnexpecedPropertyAccessOnce(propertyName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logUnexpecedPropertyAccessOnce(propertyName) {
|
||||||
|
if (loggedProperties[propertyName] !== true) {
|
||||||
|
loggedProperties[propertyName] = true;
|
||||||
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
|
||||||
|
`You can add a runtime option to disable the check or this warning:\n` +
|
||||||
|
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetLoggedProperties() {
|
||||||
|
Object.keys(loggedProperties).forEach(propertyName => {
|
||||||
|
delete loggedProperties[propertyName];
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export function wrapHelper(helper, transformOptionsFn) {
|
||||||
|
if (typeof helper !== 'function') {
|
||||||
|
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
|
||||||
|
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
|
||||||
|
return helper;
|
||||||
|
}
|
||||||
|
let wrapper = function(/* dynamic arguments */) {
|
||||||
|
const options = arguments[arguments.length - 1];
|
||||||
|
arguments[arguments.length - 1] = transformOptionsFn(options);
|
||||||
|
return helper.apply(this, arguments);
|
||||||
|
};
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import {indexOf} from './utils';
|
import { indexOf } from './utils';
|
||||||
|
|
||||||
let logger = {
|
let logger = {
|
||||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||||
@@ -22,12 +22,16 @@ let logger = {
|
|||||||
log: function(level, ...message) {
|
log: function(level, ...message) {
|
||||||
level = logger.lookupLevel(level);
|
level = logger.lookupLevel(level);
|
||||||
|
|
||||||
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
|
if (
|
||||||
|
typeof console !== 'undefined' &&
|
||||||
|
logger.lookupLevel(logger.level) <= level
|
||||||
|
) {
|
||||||
let method = logger.methodMap[level];
|
let method = logger.methodMap[level];
|
||||||
if (!console[method]) { // eslint-disable-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
if (!console[method]) {
|
||||||
method = 'log';
|
method = 'log';
|
||||||
}
|
}
|
||||||
console[method](...message); // eslint-disable-line no-console
|
console[method](...message); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
/* global window */
|
/* global globalThis */
|
||||||
export default function(Handlebars) {
|
export default function(Handlebars) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
let root = typeof global !== 'undefined' ? global : window,
|
// https://mathiasbynens.be/notes/globalthis
|
||||||
$Handlebars = root.Handlebars;
|
(function() {
|
||||||
|
if (typeof globalThis === 'object') return;
|
||||||
|
Object.prototype.__defineGetter__('__magic__', function() {
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
__magic__.globalThis = __magic__; // eslint-disable-line no-undef
|
||||||
|
delete Object.prototype.__magic__;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const $Handlebars = globalThis.Handlebars;
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Handlebars.noConflict = function() {
|
Handlebars.noConflict = function() {
|
||||||
if (root.Handlebars === Handlebars) {
|
if (globalThis.Handlebars === Handlebars) {
|
||||||
root.Handlebars = $Handlebars;
|
globalThis.Handlebars = $Handlebars;
|
||||||
}
|
}
|
||||||
return Handlebars;
|
return Handlebars;
|
||||||
};
|
};
|
||||||
|
|||||||
+219
-50
@@ -1,22 +1,48 @@
|
|||||||
import * as Utils from './utils';
|
import * as Utils from './utils';
|
||||||
import Exception from './exception';
|
import Exception from './exception';
|
||||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
|
import {
|
||||||
|
COMPILER_REVISION,
|
||||||
|
createFrame,
|
||||||
|
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||||
|
REVISION_CHANGES
|
||||||
|
} from './base';
|
||||||
|
import { moveHelperToHooks } from './helpers';
|
||||||
|
import { wrapHelper } from './internal/wrapHelper';
|
||||||
|
import {
|
||||||
|
createProtoAccessControl,
|
||||||
|
resultIsAllowed
|
||||||
|
} from './internal/proto-access';
|
||||||
|
|
||||||
export function checkRevision(compilerInfo) {
|
export function checkRevision(compilerInfo) {
|
||||||
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||||
currentRevision = COMPILER_REVISION;
|
currentRevision = COMPILER_REVISION;
|
||||||
|
|
||||||
if (compilerRevision !== currentRevision) {
|
if (
|
||||||
if (compilerRevision < currentRevision) {
|
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
|
||||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
compilerRevision <= COMPILER_REVISION
|
||||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
) {
|
||||||
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
return;
|
||||||
'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
|
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
|
||||||
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||||
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
|
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||||
}
|
throw new Exception(
|
||||||
|
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||||
|
'Please update your precompiler to a newer version (' +
|
||||||
|
runtimeVersions +
|
||||||
|
') or downgrade your runtime to an older version (' +
|
||||||
|
compilerVersions +
|
||||||
|
').'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||||
|
throw new Exception(
|
||||||
|
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||||
|
'Please update your runtime to a newer version (' +
|
||||||
|
compilerInfo[1] +
|
||||||
|
').'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,9 +58,13 @@ export function template(templateSpec, env) {
|
|||||||
templateSpec.main.decorator = templateSpec.main_d;
|
templateSpec.main.decorator = templateSpec.main_d;
|
||||||
|
|
||||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||||
// for external users to override these as psuedo-supported APIs.
|
// for external users to override these as pseudo-supported APIs.
|
||||||
env.VM.checkRevision(templateSpec.compiler);
|
env.VM.checkRevision(templateSpec.compiler);
|
||||||
|
|
||||||
|
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
|
||||||
|
const templateWasPrecompiledWithCompilerV7 =
|
||||||
|
templateSpec.compiler && templateSpec.compiler[0] === 7;
|
||||||
|
|
||||||
function invokePartialWrapper(partial, context, options) {
|
function invokePartialWrapper(partial, context, options) {
|
||||||
if (options.hash) {
|
if (options.hash) {
|
||||||
context = Utils.extend({}, context, options.hash);
|
context = Utils.extend({}, context, options.hash);
|
||||||
@@ -42,13 +72,27 @@ export function template(templateSpec, env) {
|
|||||||
options.ids[0] = true;
|
options.ids[0] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
let result = env.VM.invokePartial.call(this, partial, context, options);
|
|
||||||
|
let extendedOptions = Utils.extend({}, options, {
|
||||||
|
hooks: this.hooks,
|
||||||
|
protoAccessControl: this.protoAccessControl
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = env.VM.invokePartial.call(
|
||||||
|
this,
|
||||||
|
partial,
|
||||||
|
context,
|
||||||
|
extendedOptions
|
||||||
|
);
|
||||||
|
|
||||||
if (result == null && env.compile) {
|
if (result == null && env.compile) {
|
||||||
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
options.partials[options.name] = env.compile(
|
||||||
result = options.partials[options.name](context, options);
|
partial,
|
||||||
|
templateSpec.compilerOptions,
|
||||||
|
env
|
||||||
|
);
|
||||||
|
result = options.partials[options.name](context, extendedOptions);
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (options.indent) {
|
if (options.indent) {
|
||||||
@@ -64,22 +108,43 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
|
throw new Exception(
|
||||||
|
'The partial ' +
|
||||||
|
options.name +
|
||||||
|
' could not be compiled when running in runtime-only mode'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just add water
|
// Just add water
|
||||||
let container = {
|
let container = {
|
||||||
strict: function(obj, name) {
|
strict: function(obj, name, loc) {
|
||||||
if (!(name in obj)) {
|
if (!obj || !(name in obj)) {
|
||||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||||
|
loc: loc
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return obj[name];
|
return container.lookupProperty(obj, name);
|
||||||
|
},
|
||||||
|
lookupProperty: function(parent, propertyName) {
|
||||||
|
let result = parent[propertyName];
|
||||||
|
if (result == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
lookup: function(depths, name) {
|
lookup: function(depths, name) {
|
||||||
const len = depths.length;
|
const len = depths.length;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
if (depths[i] && depths[i][name] != null) {
|
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||||
|
if (result != null) {
|
||||||
return depths[i][name];
|
return depths[i][name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,9 +165,17 @@ export function template(templateSpec, env) {
|
|||||||
programs: [],
|
programs: [],
|
||||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||||
let programWrapper = this.programs[i],
|
let programWrapper = this.programs[i],
|
||||||
fn = this.fn(i);
|
fn = this.fn(i);
|
||||||
if (data || depths || blockParams || declaredBlockParams) {
|
if (data || depths || blockParams || declaredBlockParams) {
|
||||||
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
programWrapper = wrapProgram(
|
||||||
|
this,
|
||||||
|
i,
|
||||||
|
fn,
|
||||||
|
data,
|
||||||
|
declaredBlockParams,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
} else if (!programWrapper) {
|
} else if (!programWrapper) {
|
||||||
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||||
}
|
}
|
||||||
@@ -115,10 +188,10 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
merge: function(param, common) {
|
mergeIfNeeded: function(param, common) {
|
||||||
let obj = param || common;
|
let obj = param || common;
|
||||||
|
|
||||||
if (param && common && (param !== common)) {
|
if (param && common && param !== common) {
|
||||||
obj = Utils.extend({}, common, param);
|
obj = Utils.extend({}, common, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,37 +212,81 @@ export function template(templateSpec, env) {
|
|||||||
data = initData(context, data);
|
data = initData(context, data);
|
||||||
}
|
}
|
||||||
let depths,
|
let depths,
|
||||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||||
if (templateSpec.useDepths) {
|
if (templateSpec.useDepths) {
|
||||||
if (options.depths) {
|
if (options.depths) {
|
||||||
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
|
depths =
|
||||||
|
context != options.depths[0]
|
||||||
|
? [context].concat(options.depths)
|
||||||
|
: options.depths;
|
||||||
} else {
|
} else {
|
||||||
depths = [context];
|
depths = [context];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function main(context/*, options*/) {
|
function main(context /*, options*/) {
|
||||||
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
|
return (
|
||||||
|
'' +
|
||||||
|
templateSpec.main(
|
||||||
|
container,
|
||||||
|
context,
|
||||||
|
container.helpers,
|
||||||
|
container.partials,
|
||||||
|
data,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
|
|
||||||
|
main = executeDecorators(
|
||||||
|
templateSpec.main,
|
||||||
|
main,
|
||||||
|
container,
|
||||||
|
options.depths || [],
|
||||||
|
data,
|
||||||
|
blockParams
|
||||||
|
);
|
||||||
return main(context, options);
|
return main(context, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.isTop = true;
|
ret.isTop = true;
|
||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
container.helpers = container.merge(options.helpers, env.helpers);
|
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
||||||
|
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
||||||
|
container.helpers = mergedHelpers;
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
container.partials = container.merge(options.partials, env.partials);
|
// Use mergeIfNeeded here to prevent compiling global partials multiple times
|
||||||
|
container.partials = container.mergeIfNeeded(
|
||||||
|
options.partials,
|
||||||
|
env.partials
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||||
container.decorators = container.merge(options.decorators, env.decorators);
|
container.decorators = Utils.extend(
|
||||||
|
{},
|
||||||
|
env.decorators,
|
||||||
|
options.decorators
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container.hooks = {};
|
||||||
|
container.protoAccessControl = createProtoAccessControl(options);
|
||||||
|
|
||||||
|
let keepHelperInHelpers =
|
||||||
|
options.allowCallsToHelperMissing ||
|
||||||
|
templateWasPrecompiledWithCompilerV7;
|
||||||
|
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
||||||
|
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
||||||
} else {
|
} else {
|
||||||
|
container.protoAccessControl = options.protoAccessControl; // internal option
|
||||||
container.helpers = options.helpers;
|
container.helpers = options.helpers;
|
||||||
container.partials = options.partials;
|
container.partials = options.partials;
|
||||||
container.decorators = options.decorators;
|
container.decorators = options.decorators;
|
||||||
|
container.hooks = options.hooks;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,24 +298,47 @@ export function template(templateSpec, env) {
|
|||||||
throw new Exception('must pass parent depths');
|
throw new Exception('must pass parent depths');
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
|
return wrapProgram(
|
||||||
|
container,
|
||||||
|
i,
|
||||||
|
templateSpec[i],
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
export function wrapProgram(
|
||||||
|
container,
|
||||||
|
i,
|
||||||
|
fn,
|
||||||
|
data,
|
||||||
|
declaredBlockParams,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
) {
|
||||||
function prog(context, options = {}) {
|
function prog(context, options = {}) {
|
||||||
let currentDepths = depths;
|
let currentDepths = depths;
|
||||||
if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
|
if (
|
||||||
|
depths &&
|
||||||
|
context != depths[0] &&
|
||||||
|
!(context === container.nullContext && depths[0] === null)
|
||||||
|
) {
|
||||||
currentDepths = [context].concat(depths);
|
currentDepths = [context].concat(depths);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(container,
|
return fn(
|
||||||
context,
|
container,
|
||||||
container.helpers, container.partials,
|
context,
|
||||||
options.data || data,
|
container.helpers,
|
||||||
blockParams && [options.blockParams].concat(blockParams),
|
container.partials,
|
||||||
currentDepths);
|
options.data || data,
|
||||||
|
blockParams && [options.blockParams].concat(blockParams),
|
||||||
|
currentDepths
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
||||||
@@ -209,6 +349,9 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
|
|||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is currently part of the official API, therefore implementation details should not be changed.
|
||||||
|
*/
|
||||||
export function resolvePartial(partial, context, options) {
|
export function resolvePartial(partial, context, options) {
|
||||||
if (!partial) {
|
if (!partial) {
|
||||||
if (options.name === '@partial-block') {
|
if (options.name === '@partial-block') {
|
||||||
@@ -237,8 +380,10 @@ export function invokePartial(partial, context, options) {
|
|||||||
options.data = createFrame(options.data);
|
options.data = createFrame(options.data);
|
||||||
// Wrapper function to get access to currentPartialBlock from the closure
|
// Wrapper function to get access to currentPartialBlock from the closure
|
||||||
let fn = options.fn;
|
let fn = options.fn;
|
||||||
partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
|
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
|
||||||
|
context,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
// Restore the partial-block from the closure for the execution of the block
|
// Restore the partial-block from the closure for the execution of the block
|
||||||
// i.e. the part inside the block of the partial call.
|
// i.e. the part inside the block of the partial call.
|
||||||
options.data = createFrame(options.data);
|
options.data = createFrame(options.data);
|
||||||
@@ -261,7 +406,9 @@ export function invokePartial(partial, context, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function noop() { return ''; }
|
export function noop() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function initData(context, data) {
|
function initData(context, data) {
|
||||||
if (!data || !('root' in data)) {
|
if (!data || !('root' in data)) {
|
||||||
@@ -274,8 +421,30 @@ function initData(context, data) {
|
|||||||
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||||
if (fn.decorator) {
|
if (fn.decorator) {
|
||||||
let props = {};
|
let props = {};
|
||||||
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
prog = fn.decorator(
|
||||||
|
prog,
|
||||||
|
props,
|
||||||
|
container,
|
||||||
|
depths && depths[0],
|
||||||
|
data,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
Utils.extend(prog, props);
|
Utils.extend(prog, props);
|
||||||
}
|
}
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||||
|
Object.keys(mergedHelpers).forEach(helperName => {
|
||||||
|
let helper = mergedHelpers[helperName];
|
||||||
|
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function passLookupPropertyOption(helper, container) {
|
||||||
|
const lookupProperty = container.lookupProperty;
|
||||||
|
return wrapHelper(helper, options => {
|
||||||
|
return Utils.extend({ lookupProperty }, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
+17
-9
@@ -9,13 +9,13 @@ const escape = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const badChars = /[&<>"'`=]/g,
|
const badChars = /[&<>"'`=]/g,
|
||||||
possible = /[&<>"'`=]/;
|
possible = /[&<>"'`=]/;
|
||||||
|
|
||||||
function escapeChar(chr) {
|
function escapeChar(chr) {
|
||||||
return escape[chr];
|
return escape[chr];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extend(obj/* , ...source */) {
|
export function extend(obj /* , ...source */) {
|
||||||
for (let i = 1; i < arguments.length; i++) {
|
for (let i = 1; i < arguments.length; i++) {
|
||||||
for (let key in arguments[i]) {
|
for (let key in arguments[i]) {
|
||||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||||
@@ -39,16 +39,23 @@ let isFunction = function(value) {
|
|||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (isFunction(/x/)) {
|
if (isFunction(/x/)) {
|
||||||
isFunction = function(value) {
|
isFunction = function(value) {
|
||||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
return (
|
||||||
|
typeof value === 'function' &&
|
||||||
|
toString.call(value) === '[object Function]'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export {isFunction};
|
export { isFunction };
|
||||||
/* eslint-enable func-style */
|
/* eslint-enable func-style */
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
export const isArray = Array.isArray || function(value) {
|
export const isArray =
|
||||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
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.
|
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||||
export function indexOf(array, value) {
|
export function indexOf(array, value) {
|
||||||
@@ -60,7 +67,6 @@ export function indexOf(array, value) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function escapeExpression(string) {
|
export function escapeExpression(string) {
|
||||||
if (typeof string !== 'string') {
|
if (typeof string !== 'string') {
|
||||||
// don't escape SafeStrings, since they're already safe
|
// don't escape SafeStrings, since they're already safe
|
||||||
@@ -78,7 +84,9 @@ export function escapeExpression(string) {
|
|||||||
string = '' + string;
|
string = '' + string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!possible.test(string)) { return string; }
|
if (!possible.test(string)) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
return string.replace(badChars, escapeChar);
|
return string.replace(badChars, escapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// USAGE:
|
// USAGE:
|
||||||
// var handlebars = require('handlebars');
|
// var handlebars = require('handlebars');
|
||||||
|
/* eslint-env node */
|
||||||
/* eslint-disable no-var */
|
/* eslint-disable no-var */
|
||||||
|
|
||||||
// var local = handlebars.create();
|
// var local = handlebars.create();
|
||||||
|
|||||||
+123
-79
@@ -1,10 +1,10 @@
|
|||||||
|
/* eslint-env node */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import Async from 'async';
|
import Async from 'neo-async';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as Handlebars from './handlebars';
|
import * as Handlebars from './handlebars';
|
||||||
import {basename} from 'path';
|
import { basename } from 'path';
|
||||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||||
|
|
||||||
|
|
||||||
module.exports.loadTemplates = function(opts, callback) {
|
module.exports.loadTemplates = function(opts, callback) {
|
||||||
loadStrings(opts, function(err, strings) {
|
loadStrings(opts, function(err, strings) {
|
||||||
@@ -25,14 +25,19 @@ module.exports.loadTemplates = function(opts, callback) {
|
|||||||
|
|
||||||
function loadStrings(opts, callback) {
|
function loadStrings(opts, callback) {
|
||||||
let strings = arrayCast(opts.string),
|
let strings = arrayCast(opts.string),
|
||||||
names = arrayCast(opts.name);
|
names = arrayCast(opts.name);
|
||||||
|
|
||||||
if (names.length !== strings.length
|
if (names.length !== strings.length && strings.length > 1) {
|
||||||
&& strings.length > 1) {
|
return callback(
|
||||||
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
|
new Handlebars.Exception(
|
||||||
|
'Number of names did not match the number of string inputs'
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Async.map(strings, function(string, callback) {
|
Async.map(
|
||||||
|
strings,
|
||||||
|
function(string, callback) {
|
||||||
if (string !== '-') {
|
if (string !== '-') {
|
||||||
callback(undefined, string);
|
callback(undefined, string);
|
||||||
} else {
|
} else {
|
||||||
@@ -55,80 +60,94 @@ function loadStrings(opts, callback) {
|
|||||||
source: string
|
source: string
|
||||||
}));
|
}));
|
||||||
callback(err, strings);
|
callback(err, strings);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFiles(opts, callback) {
|
function loadFiles(opts, callback) {
|
||||||
// Build file extension pattern
|
// Build file extension pattern
|
||||||
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
let extension = (opts.extension || 'handlebars').replace(
|
||||||
|
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||||
|
function(arg) {
|
||||||
|
return '\\' + arg;
|
||||||
|
}
|
||||||
|
);
|
||||||
extension = new RegExp('\\.' + extension + '$');
|
extension = new RegExp('\\.' + extension + '$');
|
||||||
|
|
||||||
let ret = [],
|
let ret = [],
|
||||||
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
|
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
|
||||||
Async.whilst(() => queue.length, function(callback) {
|
Async.whilst(
|
||||||
let {template: path, root} = queue.shift();
|
() => queue.length,
|
||||||
|
function(callback) {
|
||||||
|
let { template: path, root } = queue.shift();
|
||||||
|
|
||||||
fs.stat(path, function(err, stat) {
|
fs.stat(path, function(err, stat) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
|
return callback(
|
||||||
}
|
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
opts.hasDirectory = true;
|
opts.hasDirectory = true;
|
||||||
|
|
||||||
fs.readdir(path, function(err, children) {
|
fs.readdir(path, function(err, children) {
|
||||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(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});
|
|
||||||
}
|
}
|
||||||
});
|
children.forEach(function(file) {
|
||||||
|
let childPath = path + '/' + file;
|
||||||
|
|
||||||
callback();
|
if (
|
||||||
});
|
extension.test(childPath) ||
|
||||||
|
fs.statSync(childPath).isDirectory()
|
||||||
|
) {
|
||||||
|
queue.push({ template: childPath, root: root || path });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fs.readFile(path, 'utf8', function(err, data) {
|
||||||
|
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||||
|
data = data.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the template name
|
||||||
|
let name = path;
|
||||||
|
if (!root) {
|
||||||
|
name = basename(name);
|
||||||
|
} else if (name.indexOf(root) === 0) {
|
||||||
|
name = name.substring(root.length + 1);
|
||||||
|
}
|
||||||
|
name = name.replace(extension, '');
|
||||||
|
|
||||||
|
ret.push({
|
||||||
|
path: path,
|
||||||
|
name: name,
|
||||||
|
source: data
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
fs.readFile(path, 'utf8', function(err, data) {
|
callback(undefined, ret);
|
||||||
/* 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) {
|
module.exports.cli = function(opts) {
|
||||||
@@ -138,7 +157,9 @@ module.exports.cli = function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.templates.length && !opts.hasDirectory) {
|
if (!opts.templates.length && !opts.hasDirectory) {
|
||||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
throw new Handlebars.Exception(
|
||||||
|
'Must define at least one template or directory.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.simple && opts.min) {
|
if (opts.simple && opts.min) {
|
||||||
@@ -147,12 +168,18 @@ module.exports.cli = function(opts) {
|
|||||||
|
|
||||||
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
||||||
if (opts.simple && multiple) {
|
if (opts.simple && multiple) {
|
||||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
throw new Handlebars.Exception(
|
||||||
|
'Unable to output multiple templates in simple mode'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force simple mode if we have only one template and it's unnamed.
|
// Force simple mode if we have only one template and it's unnamed.
|
||||||
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
|
if (
|
||||||
&& !opts.templates[0].name) {
|
!opts.amd &&
|
||||||
|
!opts.commonjs &&
|
||||||
|
opts.templates.length === 1 &&
|
||||||
|
!opts.templates[0].name
|
||||||
|
) {
|
||||||
opts.simple = true;
|
opts.simple = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +199,11 @@ module.exports.cli = function(opts) {
|
|||||||
let output = new SourceNode();
|
let output = new SourceNode();
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
if (opts.amd) {
|
if (opts.amd) {
|
||||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
output.add(
|
||||||
|
"define(['" +
|
||||||
|
opts.handlebarPath +
|
||||||
|
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||||
|
);
|
||||||
} else if (opts.commonjs) {
|
} else if (opts.commonjs) {
|
||||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||||
} else {
|
} else {
|
||||||
@@ -206,7 +237,10 @@ module.exports.cli = function(opts) {
|
|||||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||||
if (opts.map) {
|
if (opts.map) {
|
||||||
let consumer = new SourceMapConsumer(precompiled.map);
|
let consumer = new SourceMapConsumer(precompiled.map);
|
||||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
precompiled = SourceNode.fromStringWithSourceMap(
|
||||||
|
precompiled.code,
|
||||||
|
consumer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.simple) {
|
if (opts.simple) {
|
||||||
@@ -219,7 +253,14 @@ module.exports.cli = function(opts) {
|
|||||||
if (opts.amd && !multiple) {
|
if (opts.amd && !multiple) {
|
||||||
output.add('return ');
|
output.add('return ');
|
||||||
}
|
}
|
||||||
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
output.add([
|
||||||
|
objectName,
|
||||||
|
"['",
|
||||||
|
template.name,
|
||||||
|
"'] = template(",
|
||||||
|
precompiled,
|
||||||
|
');\n'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,12 +327,15 @@ function minify(output, sourceMapFile) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
// it does not exist!
|
// it does not exist!
|
||||||
console.error('Code minimization is disabled due to missing uglify-js dependency');
|
console.error(
|
||||||
|
'Code minimization is disabled due to missing uglify-js dependency'
|
||||||
|
);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
return require('uglify-js').minify(output.code, {
|
return require('uglify-js').minify(output.code, {
|
||||||
fromString: true,
|
sourceMap: {
|
||||||
outSourceMap: sourceMapFile,
|
content: output.map,
|
||||||
inSourceMap: JSON.parse(output.map)
|
url: sourceMapFile
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
'check-coverage': true,
|
||||||
|
branches: 100,
|
||||||
|
lines: 100,
|
||||||
|
functions: 100,
|
||||||
|
statements: 100,
|
||||||
|
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
|
||||||
|
reporter: 'html'
|
||||||
|
};
|
||||||
Generated
+27479
File diff suppressed because it is too large
Load Diff
+78
-21
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"barename": "handlebars",
|
"barename": "handlebars",
|
||||||
"version": "4.0.11",
|
"version": "4.7.8",
|
||||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||||
"homepage": "http://www.handlebarsjs.com/",
|
"homepage": "https://handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"mustache",
|
"mustache",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/wycats/handlebars.js.git"
|
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||||
},
|
},
|
||||||
"author": "Yehuda Katz",
|
"author": "Yehuda Katz",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,49 +21,80 @@
|
|||||||
"node": ">=0.4.7"
|
"node": ">=0.4.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^1.4.0",
|
"minimist": "^1.2.5",
|
||||||
"optimist": "^0.6.1",
|
"neo-async": "^2.6.2",
|
||||||
"source-map": "^0.4.4"
|
"source-map": "^0.6.1",
|
||||||
|
"wordwrap": "^1.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"uglify-js": "^2.6"
|
"uglify-js": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.17.1",
|
||||||
"aws-sdk": "^2.1.49",
|
"aws-sdk": "^2.1.49",
|
||||||
"babel-loader": "^5.0.0",
|
"babel-loader": "^5.0.0",
|
||||||
"babel-runtime": "^5.1.10",
|
"babel-runtime": "^5.1.10",
|
||||||
"benchmark": "~1.0",
|
"benchmark": "~1.0",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"concurrently": "^5.0.0",
|
||||||
|
"dirty-chai": "^2.0.1",
|
||||||
|
"dtslint": "^0.5.5",
|
||||||
"dustjs-linkedin": "^2.0.2",
|
"dustjs-linkedin": "^2.0.2",
|
||||||
"eco": "~1.1.0-rc-3",
|
"eco": "~1.1.0-rc-3",
|
||||||
"grunt": "~0.4.1",
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-config-prettier": "^6.7.0",
|
||||||
|
"eslint-plugin-compat": "^3.13.0",
|
||||||
|
"eslint-plugin-es5": "^1.4.1",
|
||||||
|
"fs-extra": "^8.1.0",
|
||||||
|
"grunt": "^1.0.4",
|
||||||
"grunt-babel": "^5.0.0",
|
"grunt-babel": "^5.0.0",
|
||||||
"grunt-cli": "~0.1.10",
|
"grunt-cli": "^1",
|
||||||
"grunt-contrib-clean": "0.x",
|
"grunt-contrib-clean": "^1",
|
||||||
"grunt-contrib-concat": "0.x",
|
"grunt-contrib-concat": "^1",
|
||||||
"grunt-contrib-connect": "0.x",
|
"grunt-contrib-connect": "^1",
|
||||||
"grunt-contrib-copy": "0.x",
|
"grunt-contrib-copy": "^1",
|
||||||
"grunt-contrib-requirejs": "0.x",
|
"grunt-contrib-requirejs": "^1",
|
||||||
"grunt-contrib-uglify": "0.x",
|
"grunt-contrib-uglify": "^1",
|
||||||
"grunt-contrib-watch": "0.x",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-eslint": "^17.1.0",
|
"grunt-shell": "^4.0.0",
|
||||||
"grunt-saucelabs": "8.x",
|
|
||||||
"grunt-webpack": "^1.0.8",
|
"grunt-webpack": "^1.0.8",
|
||||||
"istanbul": "^0.3.0",
|
"husky": "^3.1.0",
|
||||||
"jison": "~0.3.0",
|
"jison": "~0.3.0",
|
||||||
"mocha": "~1.20.0",
|
"lint-staged": "^9.5.0",
|
||||||
|
"mocha": "^5",
|
||||||
"mock-stdin": "^0.3.0",
|
"mock-stdin": "^0.3.0",
|
||||||
"mustache": "^2.1.3",
|
"mustache": "^2.1.3",
|
||||||
|
"nyc": "^14.1.1",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
"semver": "^5.0.1",
|
"semver": "^5.0.1",
|
||||||
|
"sinon": "^7.5.0",
|
||||||
|
"typescript": "^3.4.3",
|
||||||
"underscore": "^1.5.1",
|
"underscore": "^1.5.1",
|
||||||
"webpack": "^1.12.6",
|
"webpack": "^1.12.6",
|
||||||
"webpack-dev-server": "^1.12.1"
|
"webpack-dev-server": "^1.12.1"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"browser": "./dist/cjs/handlebars.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"handlebars": "bin/handlebars"
|
"handlebars": "bin/handlebars"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt"
|
"build": "grunt build",
|
||||||
|
"release": "npm run build && grunt release",
|
||||||
|
"format": "prettier --write '**/*.js' && eslint --fix .",
|
||||||
|
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||||
|
"lint:eslint": "eslint --max-warnings 0 .",
|
||||||
|
"lint:prettier": "prettier --check '**/*.js'",
|
||||||
|
"lint:types": "dtslint types",
|
||||||
|
"test": "npm run test:mocha",
|
||||||
|
"test:mocha": "grunt build && grunt test",
|
||||||
|
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
|
||||||
|
"test:integration": "grunt integration-tests",
|
||||||
|
"test:serve": "grunt connect:server:keepalive",
|
||||||
|
"extensive-tests-and-publish-to-aws": "npx mocha tasks/tests/ && grunt --stack extensive-tests-and-publish-to-aws",
|
||||||
|
"--- combined tasks ---": "",
|
||||||
|
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||||
},
|
},
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
@@ -73,5 +104,31 @@
|
|||||||
"buildConfig": {
|
"buildConfig": {
|
||||||
"minify": true
|
"minify": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"dist/*.js",
|
||||||
|
"dist/amd/**/*.js",
|
||||||
|
"dist/cjs/**/*.js",
|
||||||
|
"lib",
|
||||||
|
"release-notes.md",
|
||||||
|
"runtime.js",
|
||||||
|
"types/*.d.ts",
|
||||||
|
"runtime.d.ts"
|
||||||
|
],
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,css,json}": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true
|
||||||
|
};
|
||||||
+712
-147
File diff suppressed because it is too large
Load Diff
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import Handlebars = require('handlebars')
|
||||||
|
|
||||||
|
declare module "handlebars/runtime" {
|
||||||
|
|
||||||
|
}
|
||||||
+17
-6
@@ -1,14 +1,21 @@
|
|||||||
{
|
{
|
||||||
|
"extends": [
|
||||||
|
"../.eslintrc.js",
|
||||||
|
"plugin:es5/no-es2015",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"es5"
|
||||||
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
"CompilerContext": true,
|
"CompilerContext": true,
|
||||||
"Handlebars": true,
|
"Handlebars": true,
|
||||||
"handlebarsEnv": true,
|
"handlebarsEnv": true,
|
||||||
|
|
||||||
"shouldCompileTo": true,
|
"shouldCompileTo": true,
|
||||||
"shouldCompileToWithPartials": true,
|
"shouldCompileToWithPartials": true,
|
||||||
"shouldThrow": true,
|
"shouldThrow": true,
|
||||||
|
"expectTemplate": true,
|
||||||
"compileWithPartials": true,
|
"compileWithPartials": true,
|
||||||
|
|
||||||
"console": true,
|
"console": true,
|
||||||
"require": true,
|
"require": true,
|
||||||
"suite": true,
|
"suite": true,
|
||||||
@@ -21,16 +28,20 @@
|
|||||||
"start": true,
|
"start": true,
|
||||||
"stop": true,
|
"stop": true,
|
||||||
"ok": true,
|
"ok": true,
|
||||||
|
"sinon": true,
|
||||||
"strictEqual": true,
|
"strictEqual": true,
|
||||||
"define": true
|
"define": true,
|
||||||
|
"expect": true,
|
||||||
|
"chai": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"mocha": true
|
"mocha": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
// Disabling for tests, for now.
|
// Disabling for tests, for now.
|
||||||
"no-path-concat": 0,
|
"no-path-concat": "off",
|
||||||
|
|
||||||
"no-var": 0
|
"no-var": "off",
|
||||||
|
"dot-notation": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,16 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/spec/vendor/json2.js"></script>
|
||||||
<script src="/spec/env/json2.js"></script>
|
<script src="/spec/vendor/require.js"></script>
|
||||||
<script src="/spec/env/require.js"></script>
|
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
+7
-4
@@ -19,13 +19,16 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/spec/vendor/json2.js"></script>
|
||||||
<script src="/spec/env/json2.js"></script>
|
<script src="/spec/vendor/require.js"></script>
|
||||||
<script src="/spec/env/require.js"></script>
|
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -76,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{#someHelper true}}
|
||||||
|
<div>Some known helper</div>
|
||||||
|
{{#anotherHelper true}}
|
||||||
|
<div>Another known helper</div>
|
||||||
|
{{/anotherHelper}}
|
||||||
|
{{/someHelper}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div>This is a test</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div>Test Partial</div>
|
||||||
+258
-71
@@ -7,44 +7,102 @@ describe('ast', function() {
|
|||||||
|
|
||||||
describe('BlockStatement', function() {
|
describe('BlockStatement', function() {
|
||||||
it('should throw on mustache mismatch', function() {
|
it('should throw on mustache mismatch', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
function() {
|
||||||
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
|
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||||
|
},
|
||||||
|
Handlebars.Exception,
|
||||||
|
"foo doesn't match bar - 2:5"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('helpers', function() {
|
describe('helpers', function() {
|
||||||
describe('#helperExpression', function() {
|
describe('#helperExpression', function() {
|
||||||
it('should handle mustache statements', function() {
|
it('should handle mustache statements', function() {
|
||||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
|
equals(
|
||||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
|
AST.helpers.helperExpression({
|
||||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
|
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() {
|
it('should handle block statements', function() {
|
||||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
|
equals(
|
||||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
|
AST.helpers.helperExpression({
|
||||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
|
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() {
|
it('should handle subexpressions', function() {
|
||||||
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
|
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
|
||||||
});
|
});
|
||||||
it('should work with non-helper nodes', function() {
|
it('should work with non-helper nodes', function() {
|
||||||
equals(AST.helpers.helperExpression({type: 'Program'}), false);
|
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
|
||||||
|
|
||||||
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
|
equals(
|
||||||
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
|
AST.helpers.helperExpression({ type: 'PartialStatement' }),
|
||||||
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({ type: 'ContentStatement' }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({ type: 'CommentStatement' }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
|
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
|
||||||
|
|
||||||
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
|
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
|
||||||
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
|
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
|
||||||
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
|
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
|
||||||
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
|
equals(
|
||||||
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
|
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: 'Hash' }), false);
|
||||||
equals(AST.helpers.helperExpression({type: 'HashPair'}), false);
|
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -59,18 +117,21 @@ describe('ast', function() {
|
|||||||
equals(node.loc.end.column, lastColumn);
|
equals(node.loc.end.column, lastColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-disable no-multi-spaces */
|
||||||
ast = Handlebars.parse(
|
ast = Handlebars.parse(
|
||||||
'line 1 {{line1Token}}\n' // 1
|
'line 1 {{line1Token}}\n' + // 1
|
||||||
+ ' line 2 {{line2token}}\n' // 2
|
' line 2 {{line2token}}\n' + // 2
|
||||||
+ ' line 3 {{#blockHelperOnLine3}}\n' // 3
|
' line 3 {{#blockHelperOnLine3}}\n' + // 3
|
||||||
+ 'line 4{{line4token}}\n' // 4
|
'line 4{{line4token}}\n' + // 4
|
||||||
+ 'line5{{else}}\n' // 5
|
'line5{{else}}\n' + // 5
|
||||||
+ '{{line6Token}}\n' // 6
|
'{{line6Token}}\n' + // 6
|
||||||
+ '{{/blockHelperOnLine3}}\n' // 7
|
'{{/blockHelperOnLine3}}\n' + // 7
|
||||||
+ '{{#open}}\n' // 8
|
'{{#open}}\n' + // 8
|
||||||
+ '{{else inverse}}\n' // 9
|
'{{else inverse}}\n' + // 9
|
||||||
+ '{{else}}\n' // 10
|
'{{else}}\n' + // 10
|
||||||
+ '{{/open}}'); // 11
|
'{{/open}}'
|
||||||
|
); // 11
|
||||||
|
/* eslint-enable no-multi-spaces */
|
||||||
body = ast.body;
|
body = ast.body;
|
||||||
|
|
||||||
it('gets ContentNode line numbers', function() {
|
it('gets ContentNode line numbers', function() {
|
||||||
@@ -90,35 +151,71 @@ describe('ast', function() {
|
|||||||
it('gets MustacheStatement line numbers correct across newlines', function() {
|
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||||
var secondMustacheStatement = body[3];
|
var secondMustacheStatement = body[3];
|
||||||
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets the block helper information correct', function() {
|
it('gets the block helper information correct', function() {
|
||||||
var blockHelperNode = body[5];
|
var blockHelperNode = body[5];
|
||||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly records the line numbers the program of a block helper', function() {
|
it('correctly records the line numbers the program of a block helper', function() {
|
||||||
var blockHelperNode = body[5],
|
var blockHelperNode = body[5],
|
||||||
program = blockHelperNode.program;
|
program = blockHelperNode.program;
|
||||||
|
|
||||||
testColumns(program, 3, 5, 31, 5);
|
testColumns(program, 3, 5, 31, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||||
var blockHelperNode = body[5],
|
var blockHelperNode = body[5],
|
||||||
inverse = blockHelperNode.inverse;
|
inverse = blockHelperNode.inverse;
|
||||||
|
|
||||||
testColumns(inverse, 5, 7, 13, 0);
|
testColumns(inverse, 5, 7, 13, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly records the line number of chained inverses', function() {
|
it('correctly records the line number of chained inverses', function() {
|
||||||
var chainInverseNode = body[7];
|
var chainInverseNode = body[7];
|
||||||
|
|
||||||
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
||||||
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
|
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
|
||||||
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
|
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
|
||||||
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('whitespace control', function() {
|
||||||
|
describe('parse', function() {
|
||||||
|
it('mustache', function() {
|
||||||
|
var ast = Handlebars.parse(' {{~comment~}} ');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, '');
|
||||||
|
equals(ast.body[2].value, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block statements', function() {
|
||||||
|
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, '');
|
||||||
|
equals(ast.body[1].program.body[0].value, 'foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseWithoutProcessing', function() {
|
||||||
|
it('mustache', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block statements', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
' {{# comment~}} \nfoo\n {{~/comment}}'
|
||||||
|
);
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('standalone flags', function() {
|
describe('standalone flags', function() {
|
||||||
@@ -129,10 +226,70 @@ describe('ast', function() {
|
|||||||
equals(!!ast.body[2].value, true);
|
equals(!!ast.body[2].value, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('blocks - parseWithoutProcessing', function() {
|
||||||
|
it('block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
it('initial block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{# comment}} \nfoo\n {{/comment}}'
|
||||||
|
),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
});
|
||||||
|
it('mustaches with children', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{# comment}} \n{{foo}}\n {{/comment}}'
|
||||||
|
),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \n');
|
||||||
|
equals(block.program.body[1].path.original, 'foo');
|
||||||
|
equals(block.program.body[2].value, '\n ');
|
||||||
|
});
|
||||||
|
it('nested block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||||
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
|
equals(body[0].value, ' \n');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
});
|
||||||
|
it('column 0 block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it('marks block mustaches as standalone', function() {
|
it('marks block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
var ast = Handlebars.parse(
|
||||||
block = ast.body[1];
|
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
equals(ast.body[0].value, '');
|
equals(ast.body[0].value, '');
|
||||||
|
|
||||||
@@ -143,22 +300,24 @@ describe('ast', function() {
|
|||||||
});
|
});
|
||||||
it('marks initial block mustaches as standalone', function() {
|
it('marks initial block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||||
block = ast.body[0];
|
block = ast.body[0];
|
||||||
|
|
||||||
equals(block.program.body[0].value, 'foo\n');
|
equals(block.program.body[0].value, 'foo\n');
|
||||||
});
|
});
|
||||||
it('marks mustaches with children as standalone', function() {
|
it('marks mustaches with children as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||||
block = ast.body[0];
|
block = ast.body[0];
|
||||||
|
|
||||||
equals(block.program.body[0].value, '');
|
equals(block.program.body[0].value, '');
|
||||||
equals(block.program.body[1].path.original, 'foo');
|
equals(block.program.body[1].path.original, 'foo');
|
||||||
equals(block.program.body[2].value, '\n');
|
equals(block.program.body[2].value, '\n');
|
||||||
});
|
});
|
||||||
it('marks nested block mustaches as standalone', function() {
|
it('marks nested block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
var ast = Handlebars.parse(
|
||||||
body = ast.body[0].program.body,
|
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||||
block = body[1];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
equals(body[0].value, '');
|
equals(body[0].value, '');
|
||||||
|
|
||||||
@@ -168,9 +327,11 @@ describe('ast', function() {
|
|||||||
equals(body[0].value, '');
|
equals(body[0].value, '');
|
||||||
});
|
});
|
||||||
it('does not mark nested block mustaches as standalone', function() {
|
it('does not mark nested block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
var ast = Handlebars.parse(
|
||||||
body = ast.body[0].program.body,
|
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
|
||||||
block = body[1];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
equals(body[0].omit, undefined);
|
equals(body[0].omit, undefined);
|
||||||
|
|
||||||
@@ -180,9 +341,11 @@ describe('ast', function() {
|
|||||||
equals(body[0].omit, undefined);
|
equals(body[0].omit, undefined);
|
||||||
});
|
});
|
||||||
it('does not mark nested initial block mustaches as standalone', function() {
|
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}}'),
|
var ast = Handlebars.parse(
|
||||||
body = ast.body[0].program.body,
|
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
|
||||||
block = body[0];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[0];
|
||||||
|
|
||||||
equals(block.program.body[0].value, ' \nfoo\n');
|
equals(block.program.body[0].value, ' \nfoo\n');
|
||||||
equals(block.inverse.body[0].value, ' bar \n ');
|
equals(block.inverse.body[0].value, ' bar \n ');
|
||||||
@@ -191,8 +354,10 @@ describe('ast', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('marks column 0 block mustaches as standalone', function() {
|
it('marks column 0 block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
var ast = Handlebars.parse(
|
||||||
block = ast.body[1];
|
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
equals(ast.body[0].omit, undefined);
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
@@ -202,6 +367,18 @@ describe('ast', function() {
|
|||||||
equals(ast.body[2].value, '');
|
equals(ast.body[2].value, '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('partials - parseWithoutProcessing', function() {
|
||||||
|
it('simple partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[1].indent, '');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('partials', function() {
|
describe('partials', function() {
|
||||||
it('marks partial as standalone', function() {
|
it('marks partial as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{> partial }} ');
|
var ast = Handlebars.parse('{{> partial }} ');
|
||||||
@@ -221,6 +398,17 @@ describe('ast', function() {
|
|||||||
equals(ast.body[1].omit, undefined);
|
equals(ast.body[1].omit, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('comments - parseWithoutProcessing', function() {
|
||||||
|
it('simple comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('comments', function() {
|
describe('comments', function() {
|
||||||
it('marks comment as standalone', function() {
|
it('marks comment as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{! comment }} ');
|
var ast = Handlebars.parse('{{! comment }} ');
|
||||||
@@ -241,4 +429,3 @@ describe('ast', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+467
-176
@@ -6,300 +6,591 @@ beforeEach(function() {
|
|||||||
|
|
||||||
describe('basic context', function() {
|
describe('basic context', function() {
|
||||||
it('most basic', function() {
|
it('most basic', function() {
|
||||||
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ foo: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escaping', function() {
|
it('escaping', function() {
|
||||||
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
expectTemplate('\\{{foo}}')
|
||||||
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
.withInput({ foo: 'food' })
|
||||||
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
.toCompileTo('{{foo}}');
|
||||||
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
|
|
||||||
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
expectTemplate('content \\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('content {{foo}}');
|
||||||
|
|
||||||
|
expectTemplate('\\\\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('\\food');
|
||||||
|
|
||||||
|
expectTemplate('content \\\\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('content \\food');
|
||||||
|
|
||||||
|
expectTemplate('\\\\ {{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('\\\\ food');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('compiling with a basic context', function() {
|
it('compiling with a basic context', function() {
|
||||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
'It works if all the required keys are provided');
|
.withInput({
|
||||||
|
cruel: 'cruel',
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('It works if all the required keys are provided')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('compiling with a string context', function() {
|
it('compiling with a string context', function() {
|
||||||
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
|
expectTemplate('{{.}}{{length}}')
|
||||||
|
.withInput('bye')
|
||||||
|
.toCompileTo('bye3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('compiling with an undefined context', function() {
|
it('compiling with an undefined context', function() {
|
||||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
|
||||||
|
.withInput(undefined)
|
||||||
|
.toCompileTo('Goodbye\n\n!');
|
||||||
|
|
||||||
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
|
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
|
||||||
|
.withInput(undefined)
|
||||||
|
.toCompileTo('Goodbye');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('comments', function() {
|
it('comments', function() {
|
||||||
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
.withInput({
|
||||||
'comments are ignored');
|
cruel: 'cruel',
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('comments are ignored')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
|
|
||||||
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
|
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
|
||||||
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
|
||||||
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
|
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
|
||||||
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
|
'blah'
|
||||||
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
|
);
|
||||||
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
|
|
||||||
|
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
|
||||||
|
' blah'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
|
||||||
|
' blah'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('boolean', function() {
|
it('boolean', function() {
|
||||||
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
expectTemplate(string)
|
||||||
'booleans show the contents when true');
|
.withInput({
|
||||||
|
goodbye: true,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('booleans show the contents when true')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
expectTemplate(string)
|
||||||
'booleans do not show the contents when false');
|
.withInput({
|
||||||
|
goodbye: false,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('booleans do not show the contents when false')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('zeros', function() {
|
it('zeros', function() {
|
||||||
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
|
expectTemplate('num1: {{num1}}, num2: {{num2}}')
|
||||||
'num1: 42, num2: 0');
|
.withInput({
|
||||||
shouldCompileTo('num: {{.}}', 0, 'num: 0');
|
num1: 42,
|
||||||
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
|
num2: 0
|
||||||
|
})
|
||||||
|
.toCompileTo('num1: 42, num2: 0');
|
||||||
|
|
||||||
|
expectTemplate('num: {{.}}')
|
||||||
|
.withInput(0)
|
||||||
|
.toCompileTo('num: 0');
|
||||||
|
|
||||||
|
expectTemplate('num: {{num1/num2}}')
|
||||||
|
.withInput({ num1: { num2: 0 } })
|
||||||
|
.toCompileTo('num: 0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('false', function() {
|
it('false', function() {
|
||||||
/* eslint-disable no-new-wrappers */
|
/* eslint-disable no-new-wrappers */
|
||||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
expectTemplate('val1: {{val1}}, val2: {{val2}}')
|
||||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
.withInput({
|
||||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
val1: false,
|
||||||
|
val2: new Boolean(false)
|
||||||
|
})
|
||||||
|
.toCompileTo('val1: false, val2: false');
|
||||||
|
|
||||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
expectTemplate('val: {{.}}')
|
||||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
.withInput(false)
|
||||||
|
.toCompileTo('val: false');
|
||||||
|
|
||||||
|
expectTemplate('val: {{val1/val2}}')
|
||||||
|
.withInput({ val1: { val2: false } })
|
||||||
|
.toCompileTo('val: false');
|
||||||
|
|
||||||
|
expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
|
||||||
|
.withInput({
|
||||||
|
val1: false,
|
||||||
|
val2: new Boolean(false)
|
||||||
|
})
|
||||||
|
.toCompileTo('val1: false, val2: false');
|
||||||
|
|
||||||
|
expectTemplate('val: {{{val1/val2}}}')
|
||||||
|
.withInput({ val1: { val2: false } })
|
||||||
|
.toCompileTo('val: false');
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined and null', function() {
|
it('should handle undefined and null', function() {
|
||||||
shouldCompileTo('{{awesome undefined null}}',
|
expectTemplate('{{awesome undefined null}}')
|
||||||
{
|
.withInput({
|
||||||
awesome: function(_undefined, _null, options) {
|
awesome: function(_undefined, _null, options) {
|
||||||
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
|
return (
|
||||||
}
|
(_undefined === undefined) +
|
||||||
},
|
' ' +
|
||||||
'true true object');
|
(_null === null) +
|
||||||
shouldCompileTo('{{undefined}}',
|
' ' +
|
||||||
{
|
typeof options
|
||||||
'undefined': function() {
|
);
|
||||||
return 'undefined!';
|
}
|
||||||
}
|
})
|
||||||
},
|
.toCompileTo('true true object');
|
||||||
'undefined!');
|
|
||||||
shouldCompileTo('{{null}}',
|
expectTemplate('{{undefined}}')
|
||||||
{
|
.withInput({
|
||||||
'null': function() {
|
undefined: function() {
|
||||||
return 'null!';
|
return 'undefined!';
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'null!');
|
.toCompileTo('undefined!');
|
||||||
|
|
||||||
|
expectTemplate('{{null}}')
|
||||||
|
.withInput({
|
||||||
|
null: function() {
|
||||||
|
return 'null!';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('null!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('newlines', function() {
|
it('newlines', function() {
|
||||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
|
||||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
|
||||||
|
expectTemplate("Alan's\rTest").toCompileTo("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");
|
expectTemplate("Awesome's")
|
||||||
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
|
.withMessage(
|
||||||
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
|
"text is escaped so that it doesn't get caught on single quotes"
|
||||||
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
|
)
|
||||||
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
|
.toCompileTo("Awesome's");
|
||||||
|
|
||||||
|
expectTemplate('Awesome\\')
|
||||||
|
.withMessage("text is escaped so that the closing quote can't be ignored")
|
||||||
|
.toCompileTo('Awesome\\');
|
||||||
|
|
||||||
|
expectTemplate('Awesome\\\\ foo')
|
||||||
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
|
.toCompileTo('Awesome\\\\ foo');
|
||||||
|
|
||||||
|
expectTemplate('Awesome {{foo}}')
|
||||||
|
.withInput({ foo: '\\' })
|
||||||
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
|
.toCompileTo('Awesome \\');
|
||||||
|
|
||||||
|
expectTemplate(" ' ' ")
|
||||||
|
.withMessage('double quotes never produce invalid javascript')
|
||||||
|
.toCompileTo(" ' ' ");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escaping expressions', function() {
|
it('escaping expressions', function() {
|
||||||
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
expectTemplate('{{{awesome}}}')
|
||||||
"expressions with 3 handlebars aren't escaped");
|
.withInput({ awesome: "&'\\<>" })
|
||||||
|
.withMessage("expressions with 3 handlebars aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
|
|
||||||
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
expectTemplate('{{&awesome}}')
|
||||||
"expressions with {{& handlebars aren't escaped");
|
.withInput({ awesome: "&'\\<>" })
|
||||||
|
.withMessage("expressions with {{& handlebars aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
|
|
||||||
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
expectTemplate('{{awesome}}')
|
||||||
'by default expressions should be escaped');
|
.withInput({ awesome: '&"\'`\\<>' })
|
||||||
|
.withMessage('by default expressions should be escaped')
|
||||||
|
.toCompileTo('&"'`\\<>');
|
||||||
|
|
||||||
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: <b>'}, 'Escaped, <b> looks like: &lt;b&gt;',
|
expectTemplate('{{awesome}}')
|
||||||
'escaping should properly handle amperstands');
|
.withInput({ awesome: 'Escaped, <b> looks like: <b>' })
|
||||||
|
.withMessage('escaping should properly handle amperstands')
|
||||||
|
.toCompileTo('Escaped, <b> looks like: &lt;b&gt;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("functions returning safestrings shouldn't be escaped", function() {
|
it("functions returning safestrings shouldn't be escaped", function() {
|
||||||
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
|
expectTemplate('{{awesome}}')
|
||||||
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
|
.withInput({
|
||||||
"functions returning safestrings aren't escaped");
|
awesome: function() {
|
||||||
|
return new Handlebars.SafeString("&'\\<>");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage("functions returning safestrings aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('functions', function() {
|
it('functions', function() {
|
||||||
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
|
expectTemplate('{{awesome}}')
|
||||||
'functions are called and render their output');
|
.withInput({
|
||||||
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
|
awesome: function() {
|
||||||
'functions are bound to the context');
|
return 'Awesome';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('functions are called and render their output')
|
||||||
|
.toCompileTo('Awesome');
|
||||||
|
|
||||||
|
expectTemplate('{{awesome}}')
|
||||||
|
.withInput({
|
||||||
|
awesome: function() {
|
||||||
|
return this.more;
|
||||||
|
},
|
||||||
|
more: 'More awesome'
|
||||||
|
})
|
||||||
|
.withMessage('functions are bound to the context')
|
||||||
|
.toCompileTo('More awesome');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('functions with context argument', function() {
|
it('functions with context argument', function() {
|
||||||
shouldCompileTo('{{awesome frank}}',
|
expectTemplate('{{awesome frank}}')
|
||||||
{awesome: function(context) { return context; },
|
.withInput({
|
||||||
frank: 'Frank'},
|
awesome: function(context) {
|
||||||
'Frank', 'functions are called with context arguments');
|
return context;
|
||||||
|
},
|
||||||
|
frank: 'Frank'
|
||||||
|
})
|
||||||
|
.withMessage('functions are called with context arguments')
|
||||||
|
.toCompileTo('Frank');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pathed functions with context argument', function() {
|
it('pathed functions with context argument', function() {
|
||||||
shouldCompileTo('{{bar.awesome frank}}',
|
expectTemplate('{{bar.awesome frank}}')
|
||||||
{bar: {awesome: function(context) { return context; }},
|
.withInput({
|
||||||
frank: 'Frank'},
|
bar: {
|
||||||
'Frank', 'functions are called with context arguments');
|
awesome: function(context) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
frank: 'Frank'
|
||||||
|
})
|
||||||
|
.withMessage('functions are called with context arguments')
|
||||||
|
.toCompileTo('Frank');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depthed functions with context argument', function() {
|
it('depthed functions with context argument', function() {
|
||||||
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
|
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
|
||||||
{awesome: function(context) { return context; },
|
.withInput({
|
||||||
frank: 'Frank'},
|
awesome: function(context) {
|
||||||
'Frank', 'functions are called with context arguments');
|
return context;
|
||||||
|
},
|
||||||
|
frank: 'Frank'
|
||||||
|
})
|
||||||
|
.withMessage('functions are called with context arguments')
|
||||||
|
.toCompileTo('Frank');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block functions with context argument', function() {
|
it('block functions with context argument', function() {
|
||||||
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
|
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
|
||||||
{awesome: function(context, options) { return options.fn(context); }},
|
.withInput({
|
||||||
'inner 1', 'block functions are called with context and options');
|
awesome: function(context, options) {
|
||||||
|
return options.fn(context);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with context and options')
|
||||||
|
.toCompileTo('inner 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depthed block functions with context argument', function() {
|
it('depthed block functions with context argument', function() {
|
||||||
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
|
expectTemplate(
|
||||||
{value: true, awesome: function(context, options) { return options.fn(context); }},
|
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
|
||||||
'inner 1', 'block functions are called with context and options');
|
)
|
||||||
|
.withInput({
|
||||||
|
value: true,
|
||||||
|
awesome: function(context, options) {
|
||||||
|
return options.fn(context);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with context and options')
|
||||||
|
.toCompileTo('inner 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block functions without context argument', function() {
|
it('block functions without context argument', function() {
|
||||||
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
|
expectTemplate('{{#awesome}}inner{{/awesome}}')
|
||||||
{awesome: function(options) { return options.fn(this); }},
|
.withInput({
|
||||||
'inner', 'block functions are called with options');
|
awesome: function(options) {
|
||||||
});
|
return options.fn(this);
|
||||||
it('pathed block functions without context argument', function() {
|
}
|
||||||
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
|
})
|
||||||
{foo: {awesome: function() { return this; }}},
|
.withMessage('block functions are called with options')
|
||||||
'inner', 'block functions are called with options');
|
.toCompileTo('inner');
|
||||||
});
|
|
||||||
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('pathed block functions without context argument', function() {
|
||||||
|
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
|
||||||
|
.withInput({
|
||||||
|
foo: {
|
||||||
|
awesome: function() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with options')
|
||||||
|
.toCompileTo('inner');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('depthed block functions without context argument', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
value: true,
|
||||||
|
awesome: function() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with options')
|
||||||
|
.toCompileTo('inner');
|
||||||
|
});
|
||||||
|
|
||||||
it('paths with hyphens', function() {
|
it('paths with hyphens', function() {
|
||||||
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
|
expectTemplate('{{foo-bar}}')
|
||||||
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
.withInput({ 'foo-bar': 'baz' })
|
||||||
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
|
|
||||||
|
expectTemplate('{{foo.foo-bar}}')
|
||||||
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
|
|
||||||
|
expectTemplate('{{foo/foo-bar}}')
|
||||||
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested paths', function() {
|
it('nested paths', function() {
|
||||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
'Goodbye beautiful world!', 'Nested paths access nested objects');
|
.withInput({ alan: { expression: 'beautiful' } })
|
||||||
|
.withMessage('Nested paths access nested objects')
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested paths with empty string value', function() {
|
it('nested paths with empty string value', function() {
|
||||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
'Goodbye world!', 'Nested paths access nested objects with empty string');
|
.withInput({ alan: { expression: '' } })
|
||||||
|
.withMessage('Nested paths access nested objects with empty string')
|
||||||
|
.toCompileTo('Goodbye world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('literal paths', function() {
|
it('literal paths', function() {
|
||||||
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
|
expectTemplate('Goodbye {{[@alan]/expression}} world!')
|
||||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
.withInput({ '@alan': { expression: 'beautiful' } })
|
||||||
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
|
.withMessage('Literal paths can be used')
|
||||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{[foo bar]/expression}} world!')
|
||||||
|
.withInput({ 'foo bar': { expression: 'beautiful' } })
|
||||||
|
.withMessage('Literal paths can be used')
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('literal references', function() {
|
it('literal references', function() {
|
||||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
expectTemplate('Goodbye {{[foo bar]}} world!')
|
||||||
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
.toCompileTo('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!');
|
expectTemplate('Goodbye {{"foo bar"}} world!')
|
||||||
shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate("Goodbye {{'foo bar'}} world!")
|
||||||
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{"foo[bar"}} world!')
|
||||||
|
.withInput({ 'foo[bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{"foo\'bar"}} world!')
|
||||||
|
.withInput({ "foo'bar": 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate("Goodbye {{'foo\"bar'}} world!")
|
||||||
|
.withInput({ 'foo"bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||||
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
|
expectTemplate('test: {{.}}')
|
||||||
|
.withInput(null)
|
||||||
|
.withHelpers({ helper: 'awesome' })
|
||||||
|
.toCompileTo('test: ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('complex but empty paths', function() {
|
it('complex but empty paths', function() {
|
||||||
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
|
expectTemplate('{{person/name}}')
|
||||||
shouldCompileTo('{{person/name}}', {person: {}}, '');
|
.withInput({ person: { name: null } })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{person/name}}')
|
||||||
|
.withInput({ person: {} })
|
||||||
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword in paths', function() {
|
it('this keyword in paths', function() {
|
||||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
|
||||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
'This keyword in paths evaluates to current context');
|
.toCompileTo('goodbyeGoodbyeGOODBYE');
|
||||||
|
|
||||||
string = '{{#hellos}}{{this/text}}{{/hellos}}';
|
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
|
||||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
.withInput({
|
||||||
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
|
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||||
|
})
|
||||||
|
.withMessage('This keyword evaluates in more complex paths')
|
||||||
|
.toCompileTo('helloHelloHELLO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword nested inside path', function() {
|
it('this keyword nested inside path', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
|
||||||
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
|
Error,
|
||||||
}, Error, 'Invalid path: text/this - 1:13');
|
'Invalid path: text/this - 1:13'
|
||||||
|
);
|
||||||
|
|
||||||
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
|
expectTemplate('{{[this]}}')
|
||||||
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
|
.withInput({ this: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{text/[this]}}')
|
||||||
|
.withInput({ text: { this: 'bar' } })
|
||||||
|
.toCompileTo('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword in helpers', function() {
|
it('this keyword in helpers', function() {
|
||||||
var helpers = {foo: function(value) {
|
var helpers = {
|
||||||
|
foo: function(value) {
|
||||||
return 'bar ' + value;
|
return 'bar ' + value;
|
||||||
}};
|
}
|
||||||
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
|
};
|
||||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
|
||||||
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
|
|
||||||
'This keyword in paths evaluates to current context');
|
|
||||||
|
|
||||||
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
|
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
|
||||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
|
.withHelpers(helpers)
|
||||||
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
|
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
|
||||||
|
|
||||||
|
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
|
||||||
|
.withInput({
|
||||||
|
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||||
|
})
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.withMessage('This keyword evaluates in more complex paths')
|
||||||
|
.toCompileTo('bar hellobar Hellobar HELLO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword nested inside helpers param', function() {
|
it('this keyword nested inside helpers param', function() {
|
||||||
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
|
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
|
||||||
shouldThrow(function() {
|
Error,
|
||||||
CompilerContext.compile(string);
|
'Invalid path: text/this - 1:17'
|
||||||
}, Error, 'Invalid path: text/this - 1:17');
|
);
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{foo [this]}}')
|
||||||
'{{foo [this]}}',
|
.withInput({
|
||||||
{foo: function(value) { return value; }, 'this': 'bar'},
|
foo: function(value) {
|
||||||
'bar');
|
return value;
|
||||||
shouldCompileTo(
|
},
|
||||||
'{{foo text/[this]}}',
|
this: 'bar'
|
||||||
{foo: function(value) { return value; }, text: {'this': 'bar'}},
|
})
|
||||||
'bar');
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{foo text/[this]}}')
|
||||||
|
.withInput({
|
||||||
|
foo: function(value) {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
text: { this: 'bar' }
|
||||||
|
})
|
||||||
|
.toCompileTo('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass string literals', function() {
|
it('pass string literals', function() {
|
||||||
shouldCompileTo('{{"foo"}}', {}, '');
|
expectTemplate('{{"foo"}}').toCompileTo('');
|
||||||
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
|
|
||||||
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
|
expectTemplate('{{"foo"}}')
|
||||||
|
.withInput({ foo: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
|
||||||
|
.withInput({
|
||||||
|
foo: ['bar', 'baz']
|
||||||
|
})
|
||||||
|
.toCompileTo('barbaz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass number literals', function() {
|
it('pass number literals', function() {
|
||||||
shouldCompileTo('{{12}}', {}, '');
|
expectTemplate('{{12}}').toCompileTo('');
|
||||||
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
|
|
||||||
shouldCompileTo('{{12.34}}', {}, '');
|
expectTemplate('{{12}}')
|
||||||
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
|
.withInput({ '12': 'bar' })
|
||||||
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34}}')
|
||||||
|
.withInput({ '12.34': 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34 1}}')
|
||||||
|
.withInput({
|
||||||
|
'12.34': function(arg) {
|
||||||
|
return 'bar' + arg;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('bar1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass boolean literals', function() {
|
it('pass boolean literals', function() {
|
||||||
shouldCompileTo('{{true}}', {}, '');
|
expectTemplate('{{true}}').toCompileTo('');
|
||||||
shouldCompileTo('{{true}}', { '': 'foo' }, '');
|
|
||||||
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
|
expectTemplate('{{true}}')
|
||||||
|
.withInput({ '': 'foo' })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{false}}')
|
||||||
|
.withInput({ false: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle literals in subexpression', function() {
|
it('should handle literals in subexpression', function() {
|
||||||
var helpers = {
|
expectTemplate('{{foo (false)}}')
|
||||||
foo: function(arg) {
|
.withInput({
|
||||||
|
false: function() {
|
||||||
|
return 'bar';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withHelper('foo', function(arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('bar');
|
||||||
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+304
-195
@@ -1,308 +1,405 @@
|
|||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it('array', function() {
|
it('array', function() {
|
||||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
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!',
|
expectTemplate(string)
|
||||||
'Arrays ignore the contents when empty');
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays iterate over the contents when not empty')
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays ignore the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('array without data', function() {
|
it('array without data', function() {
|
||||||
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
|
||||||
shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: false })
|
||||||
|
.toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('array with @index', function() {
|
it('array with @index', function() {
|
||||||
var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('empty block', function() {
|
it('empty block', function() {
|
||||||
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
var 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!',
|
expectTemplate(string)
|
||||||
'Arrays ignore the contents when empty');
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays iterate over the contents when not empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays ignore the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with complex lookup', function() {
|
it('block with complex lookup', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
|
||||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
.withInput({
|
||||||
|
name: 'Alan',
|
||||||
shouldCompileTo(string, hash, 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
|
goodbyes: [
|
||||||
'Templates can access variables in contexts up the stack with relative path syntax');
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'Templates can access variables in contexts up the stack with relative path syntax'
|
||||||
|
)
|
||||||
|
.toCompileTo(
|
||||||
|
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiple blocks with complex lookup', function() {
|
it('multiple blocks with complex lookup', function() {
|
||||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}')
|
||||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
.withInput({
|
||||||
|
name: 'Alan',
|
||||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.toCompileTo('AlanAlanAlanAlanAlanAlan');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with complex lookup using nested context', function() {
|
it('block with complex lookup using nested context', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with deep nested complex lookup', function() {
|
it('block with deep nested complex lookup', function() {
|
||||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
|
expectTemplate(
|
||||||
var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] };
|
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
.withInput({
|
||||||
|
omg: 'OMG!',
|
||||||
|
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.toCompileTo('Goodbye cruel sad OMG!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with cached blocks', function() {
|
it('works with cached blocks', function() {
|
||||||
var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
|
expectTemplate(
|
||||||
|
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
|
||||||
var result = template({person: [{first: 'Alan', last: 'Johnson'}, {first: 'Alan', last: 'Johnson'}]});
|
)
|
||||||
equals(result, 'Alan JohnsonAlan Johnson');
|
.withCompileOptions({ data: false })
|
||||||
|
.withInput({
|
||||||
|
person: [
|
||||||
|
{ first: 'Alan', last: 'Johnson' },
|
||||||
|
{ first: 'Alan', last: 'Johnson' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan JohnsonAlan Johnson');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inverted sections', function() {
|
describe('inverted sections', function() {
|
||||||
it('inverted sections with unset value', function() {
|
it('inverted sections with unset value', function() {
|
||||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
expectTemplate(
|
||||||
var hash = {};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
|
)
|
||||||
|
.withMessage("Inverted section rendered when value isn't set.")
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inverted section with false value', function() {
|
it('inverted section with false value', function() {
|
||||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
expectTemplate(
|
||||||
var hash = {goodbyes: false};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
|
)
|
||||||
|
.withInput({ goodbyes: false })
|
||||||
|
.withMessage('Inverted section rendered when value is false.')
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inverted section with empty set', function() {
|
it('inverted section with empty set', function() {
|
||||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
expectTemplate(
|
||||||
var hash = {goodbyes: []};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
|
)
|
||||||
|
.withInput({ goodbyes: [] })
|
||||||
|
.withMessage('Inverted section rendered when value is empty set.')
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block inverted sections', function() {
|
it('block inverted sections', function() {
|
||||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
'No people');
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chained inverted sections', function() {
|
it('chained inverted sections', function() {
|
||||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'},
|
expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}')
|
||||||
'No people');
|
.withInput({ none: 'No people' })
|
||||||
shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'},
|
.toCompileTo('No people');
|
||||||
'No people');
|
|
||||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', {none: 'No people'},
|
expectTemplate(
|
||||||
'No people');
|
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chained inverted sections with mismatch', function() {
|
it('chained inverted sections with mismatch', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate(
|
||||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
|
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
|
||||||
'No people');
|
).toThrow(Error);
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block inverted sections with empty arrays', function() {
|
it('block inverted sections with empty arrays', function() {
|
||||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
'No people');
|
.withInput({
|
||||||
|
none: 'No people',
|
||||||
|
people: []
|
||||||
|
})
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('standalone sections', function() {
|
describe('standalone sections', function() {
|
||||||
it('block standalone else sections', function() {
|
it('block standalone else sections', function() {
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
'No people\n');
|
.withInput({ none: 'No people' })
|
||||||
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
|
.toCompileTo('No people\n');
|
||||||
'No people\n');
|
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n')
|
||||||
'No people\n');
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
|
|
||||||
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block standalone else sections can be disabled', function() {
|
it('block standalone else sections can be disabled', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
.withInput({ none: 'No people' })
|
||||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
'\nNo people\n\n');
|
.toCompileTo('\nNo people\n\n');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
|
||||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
.withInput({ none: 'No people' })
|
||||||
'\nNo people\n\n');
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
|
.toCompileTo('\nNo people\n\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block standalone chained else sections', function() {
|
it('block standalone chained else sections', function() {
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
expectTemplate(
|
||||||
'No people\n');
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n'
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
|
)
|
||||||
'No people\n');
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nesting', function() {
|
it('should handle nesting', function() {
|
||||||
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
|
expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
|
||||||
|
.withInput({
|
||||||
|
data: [1, 3, 5]
|
||||||
|
})
|
||||||
|
.toCompileTo('1\n3\n5\nOK.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compat mode', function() {
|
describe('compat mode', function() {
|
||||||
it('block with deep recursive lookup lookup', function() {
|
it('block with deep recursive lookup lookup', function() {
|
||||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
|
expectTemplate(
|
||||||
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
.withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with deep recursive pathed lookup', function() {
|
it('block with deep recursive pathed lookup', function() {
|
||||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
expectTemplate(
|
||||||
var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
.withInput({
|
||||||
|
omg: { yes: 'OMG!' },
|
||||||
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
});
|
});
|
||||||
it('block with missed recursive lookup', function() {
|
|
||||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
|
||||||
var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
|
||||||
|
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
|
it('block with missed recursive lookup', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
omg: { no: 'OMG!' },
|
||||||
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('decorators', function() {
|
describe('decorators', function() {
|
||||||
it('should apply mustache decorators', function() {
|
it('should apply mustache decorators', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn) {
|
|
||||||
fn.run = 'success';
|
fn.run = 'success';
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{*decorator}}{{/helper}}',
|
|
||||||
{hash: {}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply allow undefined return', function() {
|
it('should apply allow undefined return', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn() + options.fn.run;
|
return options.fn() + options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn) {
|
|
||||||
fn.run = 'cess';
|
fn.run = 'cess';
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{*decorator}}suc{{/helper}}',
|
|
||||||
{hash: {}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply block decorators', function() {
|
it('should apply block decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = options.fn();
|
fn.run = options.fn();
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
|
|
||||||
{hash: {}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support nested decorators', function() {
|
it('should support nested decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorators({
|
||||||
var decorators = {
|
decorator: function(fn, props, container, options) {
|
||||||
decorator: function(fn, props, container, options) {
|
fn.run = options.fn.nested + options.fn();
|
||||||
fn.run = options.fn.nested + options.fn();
|
return fn;
|
||||||
return fn;
|
},
|
||||||
},
|
nested: function(fn, props, container, options) {
|
||||||
nested: function(fn, props, container, options) {
|
props.nested = options.fn();
|
||||||
props.nested = options.fn();
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
|
|
||||||
{hash: {}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply multiple decorators', function() {
|
it('should apply multiple decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = (fn.run || '') + options.fn();
|
fn.run = (fn.run || '') + options.fn();
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
|
|
||||||
{hash: {}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should access parent variables', function() {
|
it('should access parent variables', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = options.args;
|
fn.run = options.args;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'success' })
|
||||||
shouldCompileTo(
|
.toCompileTo('success');
|
||||||
'{{#helper}}{{*decorator foo}}{{/helper}}',
|
|
||||||
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
|
|
||||||
'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with root program', function() {
|
it('should work with root program', function() {
|
||||||
var run;
|
var run;
|
||||||
var decorators = {
|
expectTemplate('{{*decorator "success"}}')
|
||||||
decorator: function(fn, props, container, options) {
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
equals(options.args[0], 'success');
|
equals(options.args[0], 'success');
|
||||||
run = true;
|
run = true;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'success' })
|
||||||
shouldCompileTo(
|
.toCompileTo('');
|
||||||
'{{*decorator "success"}}',
|
|
||||||
{hash: {'foo': 'success'}, decorators: decorators},
|
|
||||||
'');
|
|
||||||
equals(run, true);
|
equals(run, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail when accessing variables from root', function() {
|
it('should fail when accessing variables from root', function() {
|
||||||
var run;
|
var run;
|
||||||
var decorators = {
|
expectTemplate('{{*decorator foo}}')
|
||||||
decorator: function(fn, props, container, options) {
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
equals(options.args[0], undefined);
|
equals(options.args[0], undefined);
|
||||||
run = true;
|
run = true;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'fail' })
|
||||||
shouldCompileTo(
|
.toCompileTo('');
|
||||||
'{{*decorator foo}}',
|
|
||||||
{hash: {'foo': 'fail'}, decorators: decorators},
|
|
||||||
'');
|
|
||||||
equals(run, true);
|
equals(run, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -334,13 +431,25 @@ describe('blocks', function() {
|
|||||||
equals(handlebarsEnv.decorators.foo, undefined);
|
equals(handlebarsEnv.decorators.foo, undefined);
|
||||||
equals(handlebarsEnv.decorators.bar, undefined);
|
equals(handlebarsEnv.decorators.bar, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails with multiple and args', function() {
|
it('fails with multiple and args', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
handlebarsEnv.registerDecorator({
|
function() {
|
||||||
world: function() { return 'world!'; },
|
handlebarsEnv.registerDecorator(
|
||||||
testHelper: function() { return 'found it!'; }
|
{
|
||||||
}, {});
|
world: function() {
|
||||||
}, Error, 'Arg not supported with multiple decorators');
|
return 'world!';
|
||||||
|
},
|
||||||
|
testHelper: function() {
|
||||||
|
return 'found it!';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'Arg not supported with multiple decorators'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+543
-212
@@ -2,65 +2,184 @@ describe('builtin helpers', function() {
|
|||||||
describe('#if', function() {
|
describe('#if', function() {
|
||||||
it('if', function() {
|
it('if', function() {
|
||||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
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');
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
|
.withInput({
|
||||||
'if with string argument shows the contents');
|
goodbye: true,
|
||||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
world: 'world'
|
||||||
'if with boolean argument does not show the contents when false');
|
})
|
||||||
shouldCompileTo(string, {world: 'world'}, 'cruel world!',
|
.withMessage('if with boolean argument shows the contents when true')
|
||||||
'if with undefined does not show the contents');
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
|
|
||||||
'if with non-empty array shows the contents');
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
|
.withInput({
|
||||||
'if with empty array does not show the contents');
|
goodbye: 'dummy',
|
||||||
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
|
world: 'world'
|
||||||
'if with zero does not show the contents');
|
})
|
||||||
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
|
.withMessage('if with string argument shows the contents')
|
||||||
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
'if with zero does not show the contents');
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: false,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with boolean argument does not show the contents when false'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({ world: 'world' })
|
||||||
|
.withMessage('if with undefined does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: ['foo'],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with non-empty array shows the contents')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with empty array does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: 0,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with zero does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbye: 0,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with zero does not show the contents')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if with function argument', function() {
|
it('if with function argument', function() {
|
||||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
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');
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
|
.withInput({
|
||||||
'if with function shows the contents when function returns string');
|
goodbye: function() {
|
||||||
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
|
return true;
|
||||||
'if with function does not show the contents when returns false');
|
},
|
||||||
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
|
world: 'world'
|
||||||
'if with function does not show the contents when returns undefined');
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function shows the contents when function returns true'
|
||||||
|
)
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return this.world;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function shows the contents when function returns string'
|
||||||
|
)
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function does not show the contents when returns false'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return this.foo;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function does not show the contents when returns undefined'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not change the depth list', function() {
|
it('should not change the depth list', function() {
|
||||||
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
|
expectTemplate(
|
||||||
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
|
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
foo: { goodbye: true },
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#with', function() {
|
describe('#with', function() {
|
||||||
it('with', function() {
|
it('with', function() {
|
||||||
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
|
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
|
||||||
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
|
.withInput({
|
||||||
|
person: {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('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'}});
|
it('with with function argument', function() {
|
||||||
equals(result, 'Alan Johnson');
|
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
|
||||||
|
.withInput({
|
||||||
|
person: function() {
|
||||||
|
return {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with with else', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#with person}}Person is present{{else}}Person is not present{{/with}}'
|
||||||
|
).toCompileTo('Person is not present');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with provides block parameter', function() {
|
||||||
|
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
|
||||||
|
.withInput({
|
||||||
|
person: {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works when data is disabled', function() {
|
||||||
|
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
|
||||||
|
.withInput({ person: { first: 'Alan', last: 'Johnson' } })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,187 +192,369 @@ describe('builtin helpers', function() {
|
|||||||
|
|
||||||
it('each', function() {
|
it('each', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
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!',
|
expectTemplate(string)
|
||||||
'each with array argument iterates over the contents when not empty');
|
.withInput({
|
||||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
goodbyes: [
|
||||||
'each with array argument ignores the contents when empty');
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('each with array argument ignores the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each without data', function() {
|
it('each without data', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
.withInput({
|
||||||
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withRuntimeOptions({ data: false })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
hash = {goodbyes: 'cruel', world: 'world'};
|
expectTemplate('{{#each .}}{{.}}{{/each}}')
|
||||||
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
|
.withInput({ goodbyes: 'cruel', world: 'world' })
|
||||||
|
.withRuntimeOptions({ data: false })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('cruelworld');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each without context', function() {
|
it('each without context', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
|
||||||
shouldCompileTo(string, [,,,, ], 'cruel !');
|
.withInput(undefined)
|
||||||
|
.toCompileTo('cruel !');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with an object and @key', function() {
|
it('each with an object and @key', function() {
|
||||||
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
|
var string =
|
||||||
|
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
|
||||||
function Clazz() {
|
function Clazz() {
|
||||||
this['<b>#1</b>'] = {text: 'goodbye'};
|
this['<b>#1</b>'] = { text: 'goodbye' };
|
||||||
this[2] = {text: 'GOODBYE'};
|
this[2] = { text: 'GOODBYE' };
|
||||||
}
|
}
|
||||||
Clazz.prototype.foo = 'fail';
|
Clazz.prototype.foo = 'fail';
|
||||||
var hash = {goodbyes: new Clazz(), world: 'world'};
|
var hash = { goodbyes: new Clazz(), world: 'world' };
|
||||||
|
|
||||||
// Object property iteration order is undefined according to ECMA spec,
|
// Object property iteration order is undefined according to ECMA spec,
|
||||||
// so we need to check both possible orders
|
// so we need to check both possible orders
|
||||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||||
var actual = compileWithPartials(string, hash);
|
var actual = compileWithPartials(string, hash);
|
||||||
var expected1 = '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
var expected1 =
|
||||||
var expected2 = '2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
'<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||||
|
var expected2 =
|
||||||
|
'2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||||
|
|
||||||
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
|
equals(
|
||||||
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
|
actual === expected1 || actual === expected2,
|
||||||
|
true,
|
||||||
|
'each with object argument iterates over the contents when not empty'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: {},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with @index', function() {
|
it('each with @index', function() {
|
||||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with nested @index', function() {
|
it('each with nested @index', function() {
|
||||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
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');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with block params', function() {
|
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}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'};
|
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.toCompileTo(
|
||||||
|
'0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
var template = CompilerContext.compile(string);
|
it('each with block params and strict compilation', function() {
|
||||||
var result = template(hash);
|
expectTemplate(
|
||||||
|
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}'
|
||||||
equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
|
)
|
||||||
|
.withCompileOptions({ strict: true })
|
||||||
|
.withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] })
|
||||||
|
.toCompileTo('0. goodbye!1. Goodbye!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each object with @index', function() {
|
it('each object with @index', function() {
|
||||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: {
|
||||||
|
a: { text: 'goodbye' },
|
||||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
b: { text: 'Goodbye' },
|
||||||
|
c: { text: 'GOODBYE' }
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('each with @first', function() {
|
it('each with @first', function() {
|
||||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo('goodbye! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with nested @first', function() {
|
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}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each object with @first', function() {
|
it('each object with @first', function() {
|
||||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
|
||||||
|
world: 'world'
|
||||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo('goodbye! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with @last', function() {
|
it('each with @last', function() {
|
||||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, 'GOODBYE! cruel world!', 'The @last variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo('GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each object with @last', function() {
|
it('each object with @last', function() {
|
||||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
|
||||||
|
world: 'world'
|
||||||
equal(result, 'Goodbye! cruel world!', 'The @last variable is used');
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo('Goodbye! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with nested @last', function() {
|
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}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each with function argument', function() {
|
it('each with function argument', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
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!',
|
expectTemplate(string)
|
||||||
'each with array function argument iterates over the contents when not empty');
|
.withInput({
|
||||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
goodbyes: function() {
|
||||||
'each with array function argument ignores the contents when empty');
|
return [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array function argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array function argument ignores the contents when empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each object when last key is an empty string', function() {
|
it('each object when last key is an empty string', function() {
|
||||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: {
|
||||||
|
a: { text: 'goodbye' },
|
||||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'Empty string key is not skipped');
|
b: { text: 'Goodbye' },
|
||||||
|
'': { text: 'GOODBYE' }
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Empty string key is not skipped')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data passed to helpers', function() {
|
it('data passed to helpers', function() {
|
||||||
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
|
expectTemplate(
|
||||||
var hash = {letters: ['a', 'b', 'c']};
|
'{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({ letters: ['a', 'b', 'c'] })
|
||||||
var result = template(hash, {
|
.withMessage('should output data')
|
||||||
data: {
|
.withRuntimeOptions({
|
||||||
exclaim: '!'
|
data: {
|
||||||
}
|
exclaim: '!'
|
||||||
});
|
}
|
||||||
equal(result, 'a!b!c!', 'should output data');
|
})
|
||||||
|
.toCompileTo('a!b!c!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each on implicit context', function() {
|
it('each on implicit context', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(
|
||||||
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
|
handlebarsEnv.Exception,
|
||||||
template({});
|
'Must pass iterator to #each'
|
||||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (global.Symbol && global.Symbol.iterator) {
|
||||||
|
it('each on iterable', function() {
|
||||||
|
function Iterator(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
Iterator.prototype.next = function() {
|
||||||
|
var value = this.arr[this.index];
|
||||||
|
var done = this.index === this.arr.length;
|
||||||
|
if (!done) {
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
return { value: value, done: done };
|
||||||
|
};
|
||||||
|
function Iterable(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
}
|
||||||
|
Iterable.prototype[global.Symbol.iterator] = function() {
|
||||||
|
return new Iterator(this.arr);
|
||||||
|
};
|
||||||
|
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: new Iterable([
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
]),
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: new Iterable([]),
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument ignores the contents when empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#log', function() {
|
describe('#log', function() {
|
||||||
@@ -262,9 +563,7 @@ describe('builtin helpers', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var $log,
|
var $log, $info, $error;
|
||||||
$info,
|
|
||||||
$error;
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
$log = console.log;
|
$log = console.log;
|
||||||
$info = console.info;
|
$info = console.info;
|
||||||
@@ -277,81 +576,94 @@ describe('builtin helpers', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call logger at default level', function() {
|
it('should call logger at default level', function() {
|
||||||
var string = '{{log blah}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
|
|
||||||
var levelArg, logArg;
|
var levelArg, logArg;
|
||||||
handlebarsEnv.log = function(level, arg) {
|
handlebarsEnv.log = function(level, arg) {
|
||||||
levelArg = level;
|
levelArg = level;
|
||||||
logArg = arg;
|
logArg = arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, '', 'log should not display');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withMessage('log should not display')
|
||||||
|
.toCompileTo('');
|
||||||
equals(1, levelArg, 'should call log with 1');
|
equals(1, levelArg, 'should call log with 1');
|
||||||
equals('whee', logArg, "should call log with 'whee'");
|
equals('whee', logArg, "should call log with 'whee'");
|
||||||
});
|
});
|
||||||
it('should call logger at data level', function() {
|
|
||||||
var string = '{{log blah}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
|
|
||||||
|
it('should call logger at data level', function() {
|
||||||
var levelArg, logArg;
|
var levelArg, logArg;
|
||||||
handlebarsEnv.log = function(level, arg) {
|
handlebarsEnv.log = function(level, arg) {
|
||||||
levelArg = level;
|
levelArg = level;
|
||||||
logArg = arg;
|
logArg = arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals('03', levelArg);
|
equals('03', levelArg);
|
||||||
equals('whee', logArg);
|
equals('whee', logArg);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should output to info', function() {
|
it('should output to info', function() {
|
||||||
var string = '{{log blah}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.info = function(info) {
|
console.info = function(info) {
|
||||||
equals('whee', info);
|
equals('whee', info);
|
||||||
called = true;
|
called = true;
|
||||||
|
console.info = $info;
|
||||||
|
console.log = $log;
|
||||||
};
|
};
|
||||||
console.log = function(log) {
|
console.log = function(log) {
|
||||||
equals('whee', log);
|
equals('whee', log);
|
||||||
called = true;
|
called = true;
|
||||||
|
console.info = $info;
|
||||||
|
console.log = $log;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log at data level', function() {
|
it('should log at data level', function() {
|
||||||
var string = '{{log blah}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.error = function(log) {
|
console.error = function(log) {
|
||||||
equals('whee', log);
|
equals('whee', log);
|
||||||
called = true;
|
called = true;
|
||||||
|
console.error = $error;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle missing logger', function() {
|
it('should handle missing logger', function() {
|
||||||
var string = '{{log blah}}';
|
var called = false;
|
||||||
var hash = { blah: 'whee' },
|
|
||||||
called = false;
|
|
||||||
|
|
||||||
console.error = undefined;
|
console.error = undefined;
|
||||||
console.log = function(log) {
|
console.log = function(log) {
|
||||||
equals('whee', log);
|
equals('whee', log);
|
||||||
called = true;
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle string log levels', function() {
|
it('should handle string log levels', function() {
|
||||||
var string = '{{log blah}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.error = function(log) {
|
console.error = function(log) {
|
||||||
@@ -359,17 +671,24 @@ describe('builtin helpers', function() {
|
|||||||
called = true;
|
called = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: 'error' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
|
|
||||||
called = false;
|
called = false;
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: 'ERROR' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle hash log levels', function() {
|
it('should handle hash log levels', function() {
|
||||||
var string = '{{log blah level="error"}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.error = function(log) {
|
console.error = function(log) {
|
||||||
@@ -377,25 +696,27 @@ describe('builtin helpers', function() {
|
|||||||
called = true;
|
called = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, '');
|
expectTemplate('{{log blah level="error"}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle hash log levels', function() {
|
it('should handle hash log levels', function() {
|
||||||
var string = '{{log blah level="debug"}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called = false;
|
var called = false;
|
||||||
|
|
||||||
console.info = console.log = console.error = console.debug = function(log) {
|
console.info = console.log = console.error = console.debug = function() {
|
||||||
equals('whee', log);
|
|
||||||
called = true;
|
called = true;
|
||||||
|
console.info = console.log = console.error = console.debug = $log;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, '');
|
expectTemplate('{{log blah level="debug"}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
equals(false, called);
|
equals(false, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass multiple log arguments', function() {
|
it('should pass multiple log arguments', function() {
|
||||||
var string = '{{log blah "foo" 1}}';
|
|
||||||
var hash = { blah: 'whee' };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.info = console.log = function(log1, log2, log3) {
|
console.info = console.log = function(log1, log2, log3) {
|
||||||
@@ -403,33 +724,43 @@ describe('builtin helpers', function() {
|
|||||||
equals('foo', log2);
|
equals('foo', log2);
|
||||||
equals(1, log3);
|
equals(1, log3);
|
||||||
called = true;
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, '');
|
expectTemplate('{{log blah "foo" 1}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pass zero log arguments', function() {
|
||||||
|
var called;
|
||||||
|
|
||||||
|
console.info = console.log = function() {
|
||||||
|
expect(arguments.length).to.equal(0);
|
||||||
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
expect(called).to.be.true();
|
||||||
|
});
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#lookup', function() {
|
describe('#lookup', function() {
|
||||||
it('should lookup arbitrary content', function() {
|
it('should lookup arbitrary content', function() {
|
||||||
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
|
expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}')
|
||||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
|
||||||
|
.toCompileTo('foobar');
|
||||||
var template = CompilerContext.compile(string);
|
|
||||||
var result = template(hash);
|
|
||||||
|
|
||||||
equal(result, 'foobar');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fail on undefined value', function() {
|
it('should not fail on undefined value', function() {
|
||||||
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
|
expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}')
|
||||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
|
||||||
|
.toCompileTo('');
|
||||||
var template = CompilerContext.compile(string);
|
|
||||||
var result = template(hash);
|
|
||||||
|
|
||||||
equal(result, '');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+112
-35
@@ -13,37 +13,84 @@ describe('compiler', function() {
|
|||||||
equal(compile('foo').equals(compile('foo')), true);
|
equal(compile('foo').equals(compile('foo')), true);
|
||||||
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}}').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(
|
||||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
|
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
|
compile('{{foo.bar baz "foo" true false bat=1}}')
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{foo.bar (baz bat=1)}}').equals(
|
||||||
|
compile('{{foo.bar (baz bat=1)}}')
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should treat as not equal', function() {
|
it('should treat as not equal', function() {
|
||||||
equal(compile('foo').equals(compile('bar')), false);
|
equal(compile('foo').equals(compile('bar')), false);
|
||||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.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(
|
||||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
|
compile('{{foo.bar baz bat=1}}').equals(
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
|
compile('{{foo.bar bar bat=1}}')
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
|
),
|
||||||
|
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() {
|
describe('#compile', function() {
|
||||||
it('should fail with invalid input', function() {
|
it('should fail with invalid input', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
Handlebars.compile(null);
|
function() {
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
|
Handlebars.compile(null);
|
||||||
shouldThrow(function() {
|
},
|
||||||
Handlebars.compile({});
|
Error,
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||||
|
);
|
||||||
|
shouldThrow(
|
||||||
|
function() {
|
||||||
|
Handlebars.compile({});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include the location in the error (row and column)', function() {
|
it('should include the location in the error (row and column)', function() {
|
||||||
try {
|
try {
|
||||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||||
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
equal(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'Statement must throw exception. This line should not be executed.'
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
|
equal(
|
||||||
|
err.message,
|
||||||
|
"if doesn't match def - 2:5",
|
||||||
|
'Checking error message'
|
||||||
|
);
|
||||||
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
|
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,
|
// 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)
|
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
|
||||||
@@ -57,17 +104,28 @@ describe('compiler', function() {
|
|||||||
it('should include the location as enumerable property', function() {
|
it('should include the location as enumerable property', function() {
|
||||||
try {
|
try {
|
||||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||||
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
equal(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'Statement must throw exception. This line should not be executed.'
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
equal(err.propertyIsEnumerable('column'), true, 'Checking error column');
|
equal(
|
||||||
|
Object.prototype.propertyIsEnumerable.call(err, 'column'),
|
||||||
|
true,
|
||||||
|
'Checking error column'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can utilize AST instance', function() {
|
it('can utilize AST instance', function() {
|
||||||
equal(Handlebars.compile({
|
equal(
|
||||||
type: 'Program',
|
Handlebars.compile({
|
||||||
body: [ {type: 'ContentStatement', value: 'Hello'}]
|
type: 'Program',
|
||||||
})(), 'Hello');
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})(),
|
||||||
|
'Hello'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can pass through an empty string', function() {
|
it('can pass through an empty string', function() {
|
||||||
@@ -75,33 +133,52 @@ describe('compiler', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not modify the options.data property(GH-1327)', function() {
|
it('should not modify the options.data property(GH-1327)', function() {
|
||||||
var options = {data: [{a: 'foo'}, {a: 'bar'}]};
|
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
|
||||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||||
equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
|
equal(
|
||||||
|
JSON.stringify(options, 0, 2),
|
||||||
|
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not modify the options.knownHelpers property(GH-1327)', function() {
|
it('should not modify the options.knownHelpers property(GH-1327)', function() {
|
||||||
var options = {knownHelpers: {}};
|
var options = { knownHelpers: {} };
|
||||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||||
equal(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
|
equal(
|
||||||
|
JSON.stringify(options, 0, 2),
|
||||||
|
JSON.stringify({ knownHelpers: {} }, 0, 2)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#precompile', function() {
|
describe('#precompile', function() {
|
||||||
it('should fail with invalid input', function() {
|
it('should fail with invalid input', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
Handlebars.precompile(null);
|
function() {
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
Handlebars.precompile(null);
|
||||||
shouldThrow(function() {
|
},
|
||||||
Handlebars.precompile({});
|
Error,
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
|
'You must pass a string or Handlebars AST to Handlebars.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() {
|
it('can utilize AST instance', function() {
|
||||||
equal(/return "Hello"/.test(Handlebars.precompile({
|
equal(
|
||||||
type: 'Program',
|
/return "Hello"/.test(
|
||||||
body: [ {type: 'ContentStatement', value: 'Hello'}]
|
Handlebars.precompile({
|
||||||
})), true);
|
type: 'Program',
|
||||||
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can pass through an empty string', function() {
|
it('can pass through an empty string', function() {
|
||||||
|
|||||||
+178
-171
@@ -1,26 +1,24 @@
|
|||||||
describe('data', function() {
|
describe('data', function() {
|
||||||
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
||||||
var template = CompilerContext.compile('{{hello}}', {data: true});
|
expectTemplate('{{hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
|
||||||
return options.data.adjective + ' ' + this.noun;
|
return options.data.adjective + ' ' + this.noun;
|
||||||
}
|
})
|
||||||
};
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
|
.withInput({ noun: 'cat' })
|
||||||
var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Data output by helper')
|
||||||
equals('happy cat', result, 'Data output by helper');
|
.toCompileTo('happy cat');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be looked up via @foo', function() {
|
it('data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{@hello}}');
|
expectTemplate('{{@hello}}')
|
||||||
var result = template({}, { data: { hello: 'hello' } });
|
.withRuntimeOptions({ data: { hello: 'hello' } })
|
||||||
equals('hello', result, '@foo retrieves template data');
|
.withMessage('@foo retrieves template data')
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||||
|
|
||||||
helpers.let = function(options) {
|
helpers.let = function(options) {
|
||||||
@@ -34,238 +32,247 @@ describe('data', function() {
|
|||||||
return options.fn(this, { data: frame });
|
return options.fn(this, { data: frame });
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = template({ foo: true }, { helpers: helpers });
|
expectTemplate(
|
||||||
equals('Hello world', result, 'Automatic data was triggered');
|
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
|
||||||
|
)
|
||||||
|
.withInput({ foo: true })
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.withMessage('Automatic data was triggered')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parameter data can be looked up via @foo', function() {
|
it('parameter data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world}}');
|
expectTemplate('{{hello @world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
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() {
|
it('hash values can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{hello noun=@world}}');
|
expectTemplate('{{hello noun=@world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(options) {
|
.withHelper('hello', function(options) {
|
||||||
return 'Hello ' + options.hash.noun;
|
return 'Hello ' + options.hash.noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
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() {
|
it('nested parameter data can be looked up via @foo.bar', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
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() {
|
it('nested parameter data does not fail with @world.bar', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { foo: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello undefined');
|
||||||
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() {
|
it('parameter data throws when using complex scope references', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}';
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be functions', function() {
|
it('data can be functions', function() {
|
||||||
var template = CompilerContext.compile('{{@hello}}');
|
expectTemplate('{{@hello}}')
|
||||||
var result = template({}, { data: { hello: function() { return 'hello'; } } });
|
.withRuntimeOptions({
|
||||||
equals('hello', result);
|
data: {
|
||||||
|
hello: function() {
|
||||||
|
return 'hello';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be functions with params', function() {
|
it('data can be functions with params', function() {
|
||||||
var template = CompilerContext.compile('{{@hello "hello"}}');
|
expectTemplate('{{@hello "hello"}}')
|
||||||
var result = template({}, { data: { hello: function(arg) { return arg; } } });
|
.withRuntimeOptions({
|
||||||
equals('hello', result);
|
data: {
|
||||||
|
hello: function(arg) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data is inherited downstream', function() {
|
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 });
|
expectTemplate(
|
||||||
var helpers = {
|
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'
|
||||||
let: function(options) {
|
)
|
||||||
|
.withInput({ bar: { baz: 'hello world' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.withHelper('let', function(options) {
|
||||||
var frame = Handlebars.createFrame(options.data);
|
var frame = Handlebars.createFrame(options.data);
|
||||||
for (var prop in options.hash) {
|
for (var prop in options.hash) {
|
||||||
if (prop in options.hash) {
|
if (prop in options.hash) {
|
||||||
frame[prop] = options.hash[prop];
|
frame[prop] = options.hash[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options.fn(this, {data: frame});
|
return options.fn(this, { data: frame });
|
||||||
}
|
})
|
||||||
};
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.withMessage('data variables are inherited downstream')
|
||||||
var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
|
.toCompileTo('2hello world1');
|
||||||
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() {
|
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
||||||
var template = CompilerContext.compile('{{>myPartial}}', {data: true});
|
expectTemplate('{{>myPartial}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var partials = {
|
.withPartial('myPartial', '{{hello}}')
|
||||||
myPartial: CompilerContext.compile('{{hello}}', {data: true})
|
.withHelper('hello', function(options) {
|
||||||
};
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.data.adjective + ' ' + this.noun;
|
return options.data.adjective + ' ' + this.noun;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ noun: 'cat' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
|
.withMessage('Data output by helper inside partial')
|
||||||
equals('happy cat', result, 'Data output by helper inside partial');
|
.toCompileTo('happy cat');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
|
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});
|
expectTemplate('{{hello world}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(noun, options) {
|
||||||
hello: function(noun, options) {
|
|
||||||
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, world: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Data output by helper')
|
||||||
equals('happy world!', result, 'Data output by helper');
|
.toCompileTo('happy world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
||||||
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {data: true});
|
expectTemplate('{{#hello}}{{world}}{{/hello}}')
|
||||||
|
.withCompileOptions({
|
||||||
var helpers = {
|
data: true
|
||||||
hello: function(options) {
|
})
|
||||||
|
.withHelper('hello', function(options) {
|
||||||
return options.fn(this);
|
return options.fn(this);
|
||||||
},
|
})
|
||||||
world: function(options) {
|
.withHelper('world', function(options) {
|
||||||
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Data output by helper')
|
||||||
equals('happy world!', result, 'Data output by helper');
|
.toCompileTo('happy world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
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});
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.fn({ exclaim: '?' });
|
||||||
return options.fn({exclaim: '?'});
|
})
|
||||||
},
|
.withHelper('world', function(thing, options) {
|
||||||
world: function(thing, options) {
|
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Data output by helper')
|
||||||
equals('happy world?', result, 'Data output by helper');
|
.toCompileTo('happy world?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
|
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});
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
|
||||||
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
|
})
|
||||||
},
|
.withHelper('world', function(thing, options) {
|
||||||
world: function(thing, options) {
|
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
|
||||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy', accessData: '#win'}});
|
.withMessage('Data output by helper')
|
||||||
equals('#win happy world?', result, 'Data output by helper');
|
.toCompileTo('#win happy world?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('you can override inherited data when invoking a helper', function() {
|
it('you can override inherited data when invoking a helper', function() {
|
||||||
var template = CompilerContext.compile('{{#hello}}{{world zomg}}{{/hello}}', {data: true});
|
expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.fn(
|
||||||
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
|
{ exclaim: '?', zomg: 'world' },
|
||||||
},
|
{ data: { adjective: 'sad' } }
|
||||||
world: function(thing, options) {
|
);
|
||||||
|
})
|
||||||
|
.withHelper('world', function(thing, options) {
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'planet' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Overriden data output by helper')
|
||||||
equals('sad world?', result, 'Overriden data output by helper');
|
.toCompileTo('sad world?');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('you can override inherited data when invoking a helper with depth', function() {
|
it('you can override inherited data when invoking a helper with depth', function() {
|
||||||
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
|
||||||
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
|
})
|
||||||
},
|
.withHelper('world', function(thing, options) {
|
||||||
world: function(thing, options) {
|
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
.withMessage('Overriden data output by helper')
|
||||||
equals('sad world?', result, 'Overriden data output by helper');
|
.toCompileTo('sad world?');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('@root', function() {
|
describe('@root', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({foo: 'hello'}, { data: {} });
|
.withInput({ foo: 'hello' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.toCompileTo('hello');
|
||||||
|
|
||||||
result = template({foo: 'hello'}, {});
|
expectTemplate('{{@root.foo}}')
|
||||||
equals('hello', result);
|
.withInput({ foo: 'hello' })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passed root values take priority', function() {
|
it('passed root values take priority', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({}, { data: {root: {foo: 'hello'} } });
|
.withInput({ foo: 'should not be used' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: { root: { foo: 'hello' } } })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nesting', function() {
|
describe('nesting', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
|
expectTemplate(
|
||||||
var result = template({foo: 'hello'}, {
|
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
|
||||||
helpers: {
|
)
|
||||||
helper: function(options) {
|
.withInput({ foo: 'hello' })
|
||||||
var frame = Handlebars.createFrame(options.data);
|
.withHelper('helper', function(options) {
|
||||||
frame.depth = options.data.depth + 1;
|
var frame = Handlebars.createFrame(options.data);
|
||||||
return options.fn(this, {data: frame});
|
frame.depth = options.data.depth + 1;
|
||||||
|
return options.fn(this, { data: frame });
|
||||||
|
})
|
||||||
|
.withRuntimeOptions({
|
||||||
|
data: {
|
||||||
|
depth: 0
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
data: {
|
.toCompileTo('2 1 0');
|
||||||
depth: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
equals('2 1 0', result);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+13
-2
@@ -1,7 +1,15 @@
|
|||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
vm = require('vm');
|
vm = require('vm');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = 'no-conflict';
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
@@ -9,7 +17,10 @@ var filename = 'dist/handlebars.js';
|
|||||||
if (global.minimizedTest) {
|
if (global.minimizedTest) {
|
||||||
filename = 'dist/handlebars.min.js';
|
filename = 'dist/handlebars.min.js';
|
||||||
}
|
}
|
||||||
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
|
var distHandlebars = fs.readFileSync(
|
||||||
|
require.resolve('../../' + filename),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
vm.runInThisContext(distHandlebars, filename);
|
vm.runInThisContext(distHandlebars, filename);
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
|||||||
Vendored
+176
-12
@@ -1,3 +1,7 @@
|
|||||||
|
var global = (function() {
|
||||||
|
return this;
|
||||||
|
})();
|
||||||
|
|
||||||
var AssertError;
|
var AssertError;
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
AssertError = function AssertError(message, caller) {
|
AssertError = function AssertError(message, caller) {
|
||||||
@@ -14,29 +18,48 @@ if (Error.captureStackTrace) {
|
|||||||
AssertError = Error;
|
AssertError = Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
|
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
|
||||||
|
string,
|
||||||
|
hashOrArray,
|
||||||
|
partials,
|
||||||
|
expected,
|
||||||
|
message
|
||||||
|
) {
|
||||||
var result = compileWithPartials(string, hashOrArray, partials);
|
var result = compileWithPartials(string, hashOrArray, partials);
|
||||||
if (result !== expected) {
|
if (result !== expected) {
|
||||||
throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
|
throw new AssertError(
|
||||||
|
"'" + result + "' should === '" + expected + "': " + message,
|
||||||
|
shouldCompileToWithPartials
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||||
var template,
|
var template, ary, options;
|
||||||
ary,
|
|
||||||
options;
|
|
||||||
if (hashOrArray && hashOrArray.hash) {
|
if (hashOrArray && hashOrArray.hash) {
|
||||||
ary = [hashOrArray.hash, hashOrArray];
|
ary = [hashOrArray.hash, hashOrArray];
|
||||||
delete hashOrArray.hash;
|
delete hashOrArray.hash;
|
||||||
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||||
ary = [];
|
ary = [];
|
||||||
ary.push(hashOrArray[0]);
|
ary.push(hashOrArray[0]); // input
|
||||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||||
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
|
options =
|
||||||
|
typeof hashOrArray[3] === 'object'
|
||||||
|
? hashOrArray[3]
|
||||||
|
: { compat: hashOrArray[3] };
|
||||||
if (hashOrArray[4] != null) {
|
if (hashOrArray[4] != null) {
|
||||||
options.data = !!hashOrArray[4];
|
options.data = !!hashOrArray[4];
|
||||||
ary[1].data = hashOrArray[4];
|
ary[1].data = hashOrArray[4];
|
||||||
@@ -45,17 +68,30 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
|||||||
ary = [hashOrArray];
|
ary = [hashOrArray];
|
||||||
}
|
}
|
||||||
|
|
||||||
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
|
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
|
||||||
|
string,
|
||||||
|
options
|
||||||
|
);
|
||||||
return template.apply(this, ary);
|
return template.apply(this, ary);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/
|
||||||
|
*/
|
||||||
global.equals = global.equal = function equals(a, b, msg) {
|
global.equals = global.equal = function equals(a, b, msg) {
|
||||||
if (a !== b) {
|
if (a !== b) {
|
||||||
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
|
throw new AssertError(
|
||||||
|
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
|
||||||
|
equals
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/#method_throw
|
||||||
|
*/
|
||||||
global.shouldThrow = function(callback, type, msg) {
|
global.shouldThrow = function(callback, type, msg) {
|
||||||
var failed;
|
var failed;
|
||||||
try {
|
try {
|
||||||
@@ -65,11 +101,139 @@ global.shouldThrow = function(callback, type, msg) {
|
|||||||
if (type && !(caught instanceof type)) {
|
if (type && !(caught instanceof type)) {
|
||||||
throw new AssertError('Type failure: ' + caught);
|
throw new AssertError('Type failure: ' + caught);
|
||||||
}
|
}
|
||||||
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
|
if (
|
||||||
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
|
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) {
|
if (failed) {
|
||||||
throw new AssertError('It failed to throw', shouldThrow);
|
throw new AssertError('It failed to throw', shouldThrow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global.expectTemplate = function(templateAsString) {
|
||||||
|
return new HandlebarsTestBench(templateAsString);
|
||||||
|
};
|
||||||
|
|
||||||
|
function HandlebarsTestBench(templateAsString) {
|
||||||
|
this.templateAsString = templateAsString;
|
||||||
|
this.helpers = {};
|
||||||
|
this.partials = {};
|
||||||
|
this.decorators = {};
|
||||||
|
this.input = {};
|
||||||
|
this.message =
|
||||||
|
'Template' + templateAsString + ' does not evaluate to expected output';
|
||||||
|
this.compileOptions = {};
|
||||||
|
this.runtimeOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withInput = function(input) {
|
||||||
|
this.input = input;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
|
||||||
|
this.helpers[name] = helperFunction;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(helperFunctions).forEach(function(name) {
|
||||||
|
self.withHelper(name, helperFunctions[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
|
||||||
|
this.partials[name] = partialAsString;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withPartials = function(partials) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(partials).forEach(function(name) {
|
||||||
|
self.withPartial(name, partials[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withDecorator = function(
|
||||||
|
name,
|
||||||
|
decoratorFunction
|
||||||
|
) {
|
||||||
|
this.decorators[name] = decoratorFunction;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withDecorators = function(decorators) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(decorators).forEach(function(name) {
|
||||||
|
self.withDecorator(name, decorators[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
|
||||||
|
this.compileOptions = compileOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
|
||||||
|
this.runtimeOptions = runtimeOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withMessage = function(message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
|
||||||
|
expect(this._compileAndExecute()).to.equal(
|
||||||
|
expectedOutputAsString,
|
||||||
|
this.message
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||||
|
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
|
||||||
|
var self = this;
|
||||||
|
expect(function() {
|
||||||
|
self._compileAndExecute();
|
||||||
|
}).to.throw(errorLike, errMsgMatcher, this.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._compileAndExecute = function() {
|
||||||
|
var compile =
|
||||||
|
Object.keys(this.partials).length > 0
|
||||||
|
? CompilerContext.compileWithPartial
|
||||||
|
: CompilerContext.compile;
|
||||||
|
|
||||||
|
var combinedRuntimeOptions = this._combineRuntimeOptions();
|
||||||
|
|
||||||
|
var template = compile(this.templateAsString, this.compileOptions);
|
||||||
|
return template(this.input, combinedRuntimeOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
|
||||||
|
var self = this;
|
||||||
|
var combinedRuntimeOptions = {};
|
||||||
|
Object.keys(this.runtimeOptions).forEach(function(key) {
|
||||||
|
combinedRuntimeOptions[key] = self.runtimeOptions[key];
|
||||||
|
});
|
||||||
|
combinedRuntimeOptions.helpers = this.helpers;
|
||||||
|
combinedRuntimeOptions.partials = this.partials;
|
||||||
|
combinedRuntimeOptions.decorators = this.decorators;
|
||||||
|
return combinedRuntimeOptions;
|
||||||
|
};
|
||||||
|
|||||||
Vendored
+8
@@ -1,5 +1,13 @@
|
|||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = require('../../lib');
|
global.Handlebars = require('../../lib');
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
|||||||
Vendored
+12
-8
@@ -1,11 +1,11 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
Mocha = require('mocha'),
|
Mocha = require('mocha'),
|
||||||
path = require('path');
|
path = require('path');
|
||||||
|
|
||||||
var errors = 0,
|
var errors = 0,
|
||||||
testDir = path.dirname(__dirname),
|
testDir = path.dirname(__dirname),
|
||||||
grep = process.argv[2];
|
grep = process.argv[2];
|
||||||
|
|
||||||
// Lazy hack, but whatever
|
// Lazy hack, but whatever
|
||||||
if (grep === '--min') {
|
if (grep === '--min') {
|
||||||
@@ -13,9 +13,14 @@ if (grep === '--min') {
|
|||||||
grep = undefined;
|
grep = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = fs.readdirSync(testDir)
|
var files = fs
|
||||||
.filter(function(name) { return (/.*\.js$/).test(name); })
|
.readdirSync(testDir)
|
||||||
.map(function(name) { return testDir + path.sep + name; });
|
.filter(function(name) {
|
||||||
|
return /.*\.js$/.test(name);
|
||||||
|
})
|
||||||
|
.map(function(name) {
|
||||||
|
return testDir + path.sep + name;
|
||||||
|
});
|
||||||
|
|
||||||
if (global.minimizedTest) {
|
if (global.minimizedTest) {
|
||||||
run('./runtime', function() {
|
run('./runtime', function() {
|
||||||
@@ -37,7 +42,6 @@ if (global.minimizedTest) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function run(env, callback) {
|
function run(env, callback) {
|
||||||
var mocha = new Mocha();
|
var mocha = new Mocha();
|
||||||
mocha.ui('bdd');
|
mocha.ui('bdd');
|
||||||
|
|||||||
Vendored
+18
-3
@@ -1,7 +1,15 @@
|
|||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
vm = require('vm');
|
vm = require('vm');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = 'no-conflict';
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
@@ -9,7 +17,10 @@ var filename = 'dist/handlebars.runtime.js';
|
|||||||
if (global.minimizedTest) {
|
if (global.minimizedTest) {
|
||||||
filename = 'dist/handlebars.runtime.min.js';
|
filename = 'dist/handlebars.runtime.min.js';
|
||||||
}
|
}
|
||||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
|
vm.runInThisContext(
|
||||||
|
fs.readFileSync(__dirname + '/../../' + filename),
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
|
||||||
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||||
@@ -21,7 +32,11 @@ global.CompilerContext = {
|
|||||||
compile: function(template, options) {
|
compile: function(template, options) {
|
||||||
// Hack the compiler on to the environment for these specific tests
|
// Hack the compiler on to the environment for these specific tests
|
||||||
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
|
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
|
||||||
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
|
return compiler.precompile(
|
||||||
|
precompileTemplate,
|
||||||
|
precompileOptions,
|
||||||
|
handlebarsEnv
|
||||||
|
);
|
||||||
};
|
};
|
||||||
handlebarsEnv.parse = parse;
|
handlebarsEnv.parse = parse;
|
||||||
handlebarsEnv.Compiler = compiler.Compiler;
|
handlebarsEnv.Compiler = compiler.Compiler;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "a";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "<div>Test String</div>";
|
||||||
|
},"useData":true}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
define(["handlebars.runtime"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).empty=t({compiler:[8,">= 4.3.0"],main:function(e,t,a,n,r){return""},useData:!0})});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||||
|
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(function() {
|
||||||
|
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
templates['firstTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "<div>1</div>";
|
||||||
|
},"useData":true});
|
||||||
|
templates['secondTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "<div>2</div>";
|
||||||
|
},"useData":true});
|
||||||
|
return templates;
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
return templates['artifacts/partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "<div>Test Partial</div>";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['some-path/handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
Precompile handlebar templates.
|
||||||
|
Usage: handlebars [template|directory]...
|
||||||
|
Options:
|
||||||
|
-f, --output Output File [string]
|
||||||
|
--map Source Map File [string]
|
||||||
|
-a, --amd Exports amd style (require.js) [boolean]
|
||||||
|
-c, --commonjs Exports CommonJS style, path to Handlebars module [string] [default: null]
|
||||||
|
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string] [default: ""]
|
||||||
|
-k, --known Known helpers [string]
|
||||||
|
-o, --knownOnly Known helpers only [boolean]
|
||||||
|
-m, --min Minimize output [boolean]
|
||||||
|
-n, --namespace Template namespace [string] [default: "Handlebars.templates"]
|
||||||
|
-s, --simple Output template function only. [boolean]
|
||||||
|
-N, --name Name of passed string templates. Optional if running in a simple mode. Required when operating on
|
||||||
|
multiple templates. [string]
|
||||||
|
-i, --string Generates a template from the passed CLI argument.
|
||||||
|
"-" is treated as a special value and causes stdin to be read for the template value. [string]
|
||||||
|
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||||
|
-p, --partial Compiling a partial template [boolean]
|
||||||
|
-d, --data Include data when compiling [boolean]
|
||||||
|
-e, --extension Template extension. [string] [default: "handlebars"]
|
||||||
|
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||||
|
-v, --version Prints the current compiler version [boolean]
|
||||||
|
--help Outputs this message [boolean]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
||||||
|
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true});
|
||||||
|
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "";
|
||||||
|
},"useData":true});
|
||||||
|
return templates;
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
return templates['non.default.extension'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "<div>This is a test</div>";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user