Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16bd606fec | |||
| 90ad8d97ad | |||
| 156061eb77 | |||
| 8ba91592aa | |||
| 55e4d9d80d | |||
| bae88ebee9 | |||
| c131bab4e8 | |||
| 95f33b1e72 | |||
| 09aaa56dcd | |||
| 0d6d8c335a | |||
| 7c3944015d | |||
| 7820b207e1 | |||
| 420ac171a0 | |||
| 47adcda485 | |||
| de6ded486f | |||
| 7cf753b186 | |||
| 5a427d2d2b | |||
| 26de7bdb3d | |||
| 6e9dbac8e9 | |||
| af919d2348 | |||
| df403ed203 | |||
| 9679fe6909 | |||
| 928320524f | |||
| 725986da22 | |||
| d207ad056a | |||
| 64254b604b | |||
| 878e980c0f | |||
| f6b5a288b4 | |||
| 4ed0a625e3 | |||
| e97741f9ab | |||
| 698c8a93a4 | |||
| 12cd4ef010 | |||
| ed9e301eb8 | |||
| bbe684b7c7 | |||
| f8d7b381dc | |||
| 4cd5305c7f | |||
| d97c2e6dc0 | |||
| 1c863e34ab |
@@ -1,11 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -1,24 +0,0 @@
|
||||
.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
|
||||
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"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,
|
||||
"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-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
|
||||
// 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,
|
||||
"no-reserved-keys": 2, // Important for IE
|
||||
"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": 2,
|
||||
"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-wrap-func": 2,
|
||||
"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-line-comment": 2,
|
||||
"wrap-regex": 1,
|
||||
|
||||
"no-var": 1
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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: {}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
# Handlebars-template fixtures in test cases need deterministic eol
|
||||
*.handlebars text eol=lf
|
||||
*.hbs text eol=lf
|
||||
|
||||
# Lexer files as well
|
||||
*.l text eol=lf
|
||||
@@ -1,9 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 0
|
||||
schedule:
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-type: production
|
||||
@@ -1,89 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
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: ['16', '18', '20', '22']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
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 == '16' }}
|
||||
run: |
|
||||
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||
cd ./tests/integration/webpack-test && ./test.sh && cd -
|
||||
|
||||
browser:
|
||||
name: Test (Browser)
|
||||
runs-on: 'ubuntu-22.04'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
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
|
||||
@@ -1,38 +0,0 @@
|
||||
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 }}
|
||||
+7
-14
@@ -1,20 +1,13 @@
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
/dist/
|
||||
/tmp/
|
||||
/coverage/
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
.idea
|
||||
/yarn-error.log
|
||||
/yarn.lock
|
||||
node_modules
|
||||
/handlebars-release.tgz
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
lib/handlebars/compiler/parser.js
|
||||
/coverage/
|
||||
/dist/
|
||||
/test-results/
|
||||
/tests/integration/*/dist/
|
||||
/spec/tmp/*
|
||||
sauce_connect.log*
|
||||
/.idea/
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = https://github.com/mustache/spec.git
|
||||
url = git://github.com/mustache/spec.git
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**']
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
.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/*
|
||||
@@ -1,22 +0,0 @@
|
||||
.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
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
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: '10'
|
||||
env:
|
||||
- PUBLISH=true
|
||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
||||
- node_js: '8'
|
||||
- node_js: '6'
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
+40
-131
@@ -2,26 +2,27 @@
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
|
||||
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
|
||||
## Branches
|
||||
|
||||
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||
* The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||
* The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||
should merge the branch `4.x` into the master branch regularly.
|
||||
* The branch `3.x` contains the previous release. Relevant bugfixes may be cherry-picked into this
|
||||
branch from the `4.x` branch. There should be no merges anymore between this branch and `4.x` or `master`.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Generally we like to see pull requests that
|
||||
|
||||
- Maintain the existing code style
|
||||
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
@@ -32,8 +33,8 @@ Generally we like to see pull requests that
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
- Node.js
|
||||
- [Grunt](http://gruntjs.com/getting-started)
|
||||
* Node.js
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
|
||||
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
|
||||
|
||||
@@ -47,18 +48,16 @@ You can also run our set of benchmarks with `grunt bench`.
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run tests locally, first install all dependencies.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Clone the mustache specs into the spec/mustache folder.
|
||||
|
||||
```sh
|
||||
cd spec
|
||||
rm -r mustache
|
||||
@@ -66,141 +65,51 @@ git clone https://github.com/mustache/spec.git mustache
|
||||
```
|
||||
|
||||
From the root directory, run the tests.
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
## Linting and Formatting
|
||||
## Ember testing
|
||||
|
||||
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
|
||||
We do linting and formatting in two phases:
|
||||
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.
|
||||
|
||||
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||
while warnings are allowed.
|
||||
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||
are forbidden.
|
||||
```sh
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
|
||||
You can use the following scripts to make sure that the CI job does not fail:
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm test
|
||||
```
|
||||
|
||||
- **npm run lint** will run `eslint` and fail on warnings
|
||||
- **npm run format** will run `prettier` on all files
|
||||
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||
## Releasing
|
||||
|
||||
## Releasing the latest version
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
|
||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||
Before starting the release, make sure that the `yo`, `generator-release` and `grunt` are installed globally.
|
||||
The last release was successfully performed with `yo@2.0.5`, `generator-release@2.6.1` and `grunt@1.0.3` and `node@10.11.0`.
|
||||
If the latest versions of the tools don't work you may want to revert to those versions.
|
||||
|
||||
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
|
||||
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
|
||||
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
|
||||
- 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)
|
||||
```bash
|
||||
npm install -g yo generator-release grunt
|
||||
```
|
||||
|
||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||
A full release may be completed with the following:
|
||||
|
||||
A full release via Docker may be completed with the following:
|
||||
```
|
||||
yo release
|
||||
npm publish
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
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 access token! (Used for reading issues and pull requests.)
|
||||
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub personal access token'\n};" > /home/node/.config/generator-release
|
||||
RUN chown -R node:node /home/node/.config
|
||||
RUN chown -R node:node /home/node/.ssh
|
||||
RUN chown -R node:node /home/node/tmp
|
||||
|
||||
# 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 install
|
||||
npm install -g yo@1 grunt@1 generator-release
|
||||
npm run release
|
||||
# Warning! This step will collect data from GitHub, bump the version,
|
||||
# create a new commit, create a new tag and push it to GitHub.
|
||||
# https://github.com/kpdecker/generator-release?tab=readme-ov-file#usage
|
||||
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 \
|
||||
ruby:3.2-slim bash
|
||||
```
|
||||
* Execute the following steps:
|
||||
```bash
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
gem push handlebars-source-*.gem
|
||||
```
|
||||
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).
|
||||
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.
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://jsfiddle.net/9D88g/26/
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
1. How can I file a bug report:
|
||||
|
||||
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why isn't my Mustache template working?
|
||||
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/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/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
|
||||
1. Why is it slower when compiling?
|
||||
|
||||
@@ -36,18 +36,16 @@
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
|
||||
If using the integrated precompiler and
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
|
||||
On the client side.
|
||||
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/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/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why doesn't IE like the `default` name in the AMD module?
|
||||
|
||||
@@ -55,8 +53,8 @@
|
||||
|
||||
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/handlebars-lang/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/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||
|
||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||
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.
|
||||
|
||||
+93
-94
@@ -1,78 +1,72 @@
|
||||
/* eslint-disable no-process-env */
|
||||
/*eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
clean: [
|
||||
'tmp',
|
||||
'dist',
|
||||
'lib/handlebars/compiler/parser.js',
|
||||
'/tests/integration/**/node_modules'
|
||||
],
|
||||
eslint: {
|
||||
options: {
|
||||
},
|
||||
files: [
|
||||
'*.js',
|
||||
'lib/**/!(*.min|parser).js',
|
||||
'spec/**/!(*.amd|json2|require).js'
|
||||
]
|
||||
},
|
||||
|
||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content) {
|
||||
return (
|
||||
grunt.template.process(
|
||||
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||
) + content
|
||||
);
|
||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
||||
+ content;
|
||||
}
|
||||
},
|
||||
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
|
||||
files: [
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
|
||||
]
|
||||
},
|
||||
cdnjs: {
|
||||
files: [
|
||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
|
||||
]
|
||||
},
|
||||
components: {
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'components/',
|
||||
src: ['**'],
|
||||
dest: 'dist/components'
|
||||
},
|
||||
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
|
||||
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMaps: 'inline',
|
||||
loose: ['es6.modules'],
|
||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||
loose: ['es6.modules']
|
||||
},
|
||||
amd: {
|
||||
options: {
|
||||
modules: 'amd'
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/amd/'
|
||||
}
|
||||
]
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/amd/'
|
||||
}]
|
||||
},
|
||||
|
||||
cjs: {
|
||||
options: {
|
||||
modules: 'common'
|
||||
},
|
||||
files: [
|
||||
{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/cjs/'
|
||||
}
|
||||
]
|
||||
files: [{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/cjs/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
@@ -81,12 +75,7 @@ module.exports = function(grunt) {
|
||||
module: {
|
||||
loaders: [
|
||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader:
|
||||
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
|
||||
}
|
||||
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules' }
|
||||
]
|
||||
},
|
||||
output: {
|
||||
@@ -132,20 +121,18 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
||||
preserveComments: 'some'
|
||||
},
|
||||
dist: {
|
||||
files: [
|
||||
{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function(dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
}
|
||||
files: [{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function(dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -165,10 +152,34 @@ module.exports = function(grunt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
shell: {
|
||||
integrationTests: {
|
||||
command: './tests/integration/run-integration-tests.sh'
|
||||
'saucelabs-mocha': {
|
||||
all: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
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'},
|
||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
|
||||
]
|
||||
}
|
||||
},
|
||||
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'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -179,11 +190,25 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['on-file-change']
|
||||
tasks: ['build', 'amd', 'tests', 'test']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
@@ -193,45 +218,19 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('node', ['babel:cjs']);
|
||||
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('bench', ['metrics']);
|
||||
// remove this comment when saucelabs is working again
|
||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests' /*, 'connect' , 'saucelabs-mocha' */ ] : []);
|
||||
|
||||
// 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']);
|
||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
||||
|
||||
// === Primary tasks ===
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('prepare', ['build', 'concat:tests']);
|
||||
grunt.registerTask(
|
||||
'build',
|
||||
'Builds a distributable version of the current project',
|
||||
['parser', 'node', 'amd', 'globals']
|
||||
);
|
||||
grunt.registerTask('integration-tests', [
|
||||
'default',
|
||||
'shell:integrationTests'
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2011-2019 by Yehuda Katz
|
||||
Copyright (C) 2011-2014 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+304
-32
@@ -1,29 +1,40 @@
|
||||
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||
[](https://www.npmjs.com/package/handlebars)
|
||||
[](https://www.npmjs.com/package/handlebars)
|
||||
[](https://bundlephobia.com/package/handlebars)
|
||||
[](https://packagephobia.com/result?p=handlebars)
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://ci.appveyor.com/project/wycats/handlebars-js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
|
||||
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||
Handlebars.js is an extension to the [Mustache templating
|
||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
||||
Handlebars.js and Mustache are both logicless templating languages that
|
||||
keep the view and the code separated like we all know they should be.
|
||||
|
||||
Checkout the official Handlebars docs site at
|
||||
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
|
||||
|
||||
See our [installation documentation](https://handlebarsjs.com/installation/).
|
||||
For web browsers, a free CDN is available at [jsDelivr](http://www.jsdelivr.com/#!handlebarsjs). Advanced usage, such as [version aliasing & concocting](https://github.com/jsdelivr/jsdelivr#usage), is available.
|
||||
|
||||
Alternatively, if you prefer having the latest version of handlebars from
|
||||
the 'master' branch, passing builds of the 'master' branch are automatically
|
||||
published to S3. You may download the latest passing master build by grabbing
|
||||
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
|
||||
build is published, it is also available as a `handlebars-gitSHA.js` file on
|
||||
the builds page if you need a version to refer to others.
|
||||
`handlebars-runtime.js` builds are also available.
|
||||
|
||||
**Note**: The S3 builds page is provided as a convenience for the community,
|
||||
but you should not use it for hosting Handlebars in production.
|
||||
|
||||
Usage
|
||||
-----
|
||||
In general, the syntax of Handlebars.js templates is a superset
|
||||
of Mustache templates. For basic syntax, check out the [Mustache
|
||||
manpage](https://mustache.github.io/mustache.5.html).
|
||||
manpage](http://mustache.github.com/mustache.5.html).
|
||||
|
||||
Once you have a template, use the `Handlebars.compile` method to compile
|
||||
the template into a function. The generated function takes a context
|
||||
@@ -47,34 +58,295 @@ var result = template(data);
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
Registering Helpers
|
||||
-------------------
|
||||
|
||||
You can register helpers that Handlebars will use when evaluating your
|
||||
template. Here's an example, which assumes that your objects have a URL
|
||||
embedded in them, as well as the text for a link:
|
||||
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function() {
|
||||
return new Handlebars.SafeString("<a href='" + Handlebars.Utils.escapeExpression(this.url) + "'>" + Handlebars.Utils.escapeExpression(this.body) + "</a>");
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{link_to}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Helpers take precedence over fields defined on the context. To access a field
|
||||
that is masked by a helper, a path reference may be used. In the example above
|
||||
a field named `link_to` on the `context` object would be referenced using:
|
||||
|
||||
```
|
||||
{{./link_to}}
|
||||
```
|
||||
|
||||
Escaping
|
||||
--------
|
||||
|
||||
By default, the `{{expression}}` syntax will escape its contents. This
|
||||
helps to protect you against accidental XSS problems caused by malicious
|
||||
data passed from the server as JSON.
|
||||
|
||||
To explicitly *not* escape the contents, use the triple-mustache
|
||||
(`{{{}}}`). You have seen this used in the above example.
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
|
||||
|
||||
Differences Between Handlebars.js and Mustache
|
||||
----------------------------------------------
|
||||
Handlebars.js adds a couple of additional features to make writing
|
||||
templates easier and also changes a tiny detail of how partials work.
|
||||
|
||||
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
|
||||
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
|
||||
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
|
||||
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
||||
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
||||
### Paths
|
||||
|
||||
Handlebars.js supports an extended expression syntax that we call paths.
|
||||
Paths are made up of typical expressions and `.` characters. Expressions
|
||||
allow you to not only display data from the current context, but to
|
||||
display data from contexts that are descendants and ancestors of the
|
||||
current context.
|
||||
|
||||
To display data from descendant contexts, use the `.` character. So, for
|
||||
example, if your data were structured like:
|
||||
|
||||
```js
|
||||
var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } };
|
||||
```
|
||||
|
||||
You could display the person's name from the top-level context with the
|
||||
following expression:
|
||||
|
||||
```
|
||||
{{person.name}}
|
||||
```
|
||||
|
||||
You can backtrack using `../`. For example, if you've already traversed
|
||||
into the person object you could still display the company's name with
|
||||
an expression like `{{../company.name}}`, so:
|
||||
|
||||
```
|
||||
{{#with person}}{{name}} - {{../company.name}}{{/with}}
|
||||
```
|
||||
|
||||
would render:
|
||||
|
||||
```
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
When calling a helper, you can pass paths or Strings as parameters. For
|
||||
instance:
|
||||
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function(title, options) {
|
||||
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/posts/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
When you pass a String as a parameter to a helper, the literal String
|
||||
gets passed to the helper function.
|
||||
|
||||
|
||||
### Block Helpers
|
||||
|
||||
Handlebars.js also adds the ability to define block helpers. Block
|
||||
helpers are functions that can be called from anywhere in the template.
|
||||
Here's an example:
|
||||
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Whenever the block helper is called it is given one or more parameters,
|
||||
any arguments that are passed into the helper in the call, and an `options`
|
||||
object containing the `fn` function which executes the block's child.
|
||||
The block's current context may be accessed through `this`.
|
||||
|
||||
Block helpers have the same syntax as mustache sections but should not be
|
||||
confused with one another. Sections are akin to an implicit `each` or
|
||||
`with` statement depending on the input data and helpers are explicit
|
||||
pieces of code that are free to implement whatever behavior they like.
|
||||
The [mustache spec](http://mustache.github.io/mustache.5.html)
|
||||
defines the exact behavior of sections. In the case of name conflicts,
|
||||
helpers are given priority.
|
||||
|
||||
### Partials
|
||||
|
||||
You can register additional templates as partials, which will be used by
|
||||
Handlebars when it encounters a partial (`{{> partialName}}`). Partials
|
||||
can either be String templates or compiled template functions. Here's an
|
||||
example:
|
||||
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Partials can also accept parameters
|
||||
|
||||
```js
|
||||
var source = "<div>{{> roster rosterProperties people=listOfPeople}}</div>";
|
||||
|
||||
Handlebars.registerPartial('roster', '<h2>{{rosterName}}</h2>{{#people}}<span>{{id}}: {{name}}</span>{{/people}}')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = {
|
||||
"listOfPeople": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
],
|
||||
"rosterProperties": {
|
||||
"rosterName": "Cool People"
|
||||
}
|
||||
};
|
||||
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <div>
|
||||
// <h2>Cool People</h2>
|
||||
// <span>1: Alan</span>
|
||||
// <span>2: Yehuda</span>
|
||||
// </div>
|
||||
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
You can add comments to your templates with the following syntax:
|
||||
|
||||
```js
|
||||
{{! This is a comment }}
|
||||
```
|
||||
|
||||
You can also use real html comments if you want them to end up in the output.
|
||||
|
||||
```html
|
||||
<div>
|
||||
{{! This comment will not end up in the output }}
|
||||
<!-- This comment will show up in the output -->
|
||||
</div>
|
||||
```
|
||||
|
||||
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimiters are not supported.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimeters are not supported.
|
||||
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript
|
||||
code rather than the handlebars template allowing for faster startup time.
|
||||
|
||||
### Installation
|
||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
||||
command.
|
||||
|
||||
### Usage
|
||||
|
||||
<pre>
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
|
||||
-n, --namespace Template namespace [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
</pre>
|
||||
|
||||
If using the precompiler's normal mode, the resulting templates will be
|
||||
stored to the `Handlebars.templates` object using the relative template
|
||||
name sans the extension. These templates may be executed in the same
|
||||
manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single
|
||||
javascript method. To execute this method it must be passed to
|
||||
the `Handlebars.template` method and the resulting object may be used as normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
- Rather than using the full _handlebars.js_ library, implementations that
|
||||
do not need to compile templates at runtime may include _handlebars.runtime.js_
|
||||
whose min+gzip size is approximately 1k.
|
||||
- If a helper is known to exist in the target environment they may be defined
|
||||
using the `--known name` argument may be used to optimize accesses to these
|
||||
helpers for size and speed.
|
||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
||||
to optimize all block helper references.
|
||||
- Implementations that do not use `@data` variables can improve performance of
|
||||
iteration centric templates by specifying `{data: false}` in the compiler options.
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
|
||||
@@ -90,6 +362,8 @@ 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
|
||||
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
|
||||
-----------
|
||||
|
||||
@@ -101,18 +375,18 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
|
||||
|
||||
Handlebars in the Wild
|
||||
@@ -121,7 +395,6 @@ Handlebars in the Wild
|
||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
@@ -142,16 +415,13 @@ Handlebars in the Wild
|
||||
Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||
handlebars projects.
|
||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
||||
supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||
templating engine, extending it with automatic data binding support.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
|
||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime
|
||||
|
||||
External Resources
|
||||
------------------
|
||||
@@ -164,4 +434,6 @@ License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Test against these versions of Node.js
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# Install scripts (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node.js
|
||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||
# Clone submodules (mustache spec)
|
||||
- cmd: git submodule update --init --recursive
|
||||
# Install modules
|
||||
- cmd: npm install
|
||||
- cmd: npm install -g grunt-cli
|
||||
|
||||
|
||||
# Post-install test scripts
|
||||
test_script:
|
||||
# Output useful info for debugging
|
||||
- cmd: node --version
|
||||
- cmd: npm --version
|
||||
# Run tests
|
||||
- cmd: grunt --stack travis
|
||||
|
||||
# Don't actually build
|
||||
build: off
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a coverage.zip coverage
|
||||
- cmd: appveyor PushArtifact coverage.zip
|
||||
|
||||
|
||||
# Set build version format here instead of in the admin panel
|
||||
version: "{build}"
|
||||
@@ -1,14 +1,13 @@
|
||||
var async = require('neo-async'),
|
||||
fs = require('fs'),
|
||||
zlib = require('zlib');
|
||||
var _ = require('underscore'),
|
||||
async = require('async'),
|
||||
fs = require('fs'),
|
||||
zlib = require('zlib');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
var distFiles = fs.readdirSync('dist'),
|
||||
distSizes = {};
|
||||
distSizes = {};
|
||||
|
||||
async.each(
|
||||
distFiles,
|
||||
function(file, callback) {
|
||||
async.each(distFiles, function(file, callback) {
|
||||
var content;
|
||||
try {
|
||||
content = fs.readFileSync('dist/' + file);
|
||||
@@ -34,10 +33,7 @@ module.exports = function(grunt, callback) {
|
||||
});
|
||||
},
|
||||
function() {
|
||||
grunt.log.writeln(
|
||||
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
|
||||
);
|
||||
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
|
||||
callback([distSizes]);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -1,24 +1,19 @@
|
||||
var _ = require('underscore'),
|
||||
templates = require('./templates');
|
||||
templates = require('./templates');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
||||
var Handlebars = require('../../lib');
|
||||
var Handlebars = require('../lib');
|
||||
|
||||
var templateSizes = {};
|
||||
_.each(templates, function(info, template) {
|
||||
var src = info.handlebars,
|
||||
compiled = Handlebars.precompile(src, {}),
|
||||
knownHelpers = Handlebars.precompile(src, {
|
||||
knownHelpersOnly: true,
|
||||
knownHelpers: info.helpers
|
||||
});
|
||||
compiled = Handlebars.precompile(src, {}),
|
||||
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
|
||||
|
||||
templateSizes[template] = compiled.length;
|
||||
templateSizes['knownOnly_' + template] = knownHelpers.length;
|
||||
});
|
||||
grunt.log.writeln(
|
||||
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
|
||||
);
|
||||
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
|
||||
callback([templateSizes]);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
foo: function(options) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
context: {
|
||||
bar: true
|
||||
},
|
||||
|
||||
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
||||
dust: "{#names}{name}{/names}",
|
||||
mustache: "{{#names}}{{name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#names}}{{name}}{{/names}}"
|
||||
}
|
||||
@@ -3,13 +3,13 @@ var fs = require('fs');
|
||||
module.exports = {
|
||||
context: {
|
||||
header: function() {
|
||||
return 'Colors';
|
||||
return "Colors";
|
||||
},
|
||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
||||
items: [
|
||||
{ name: 'red', current: true, url: '#Red' },
|
||||
{ name: 'green', current: false, url: '#Green' },
|
||||
{ name: 'blue', current: false, url: '#Blue' }
|
||||
{name: "red", current: true, url: "#Red"},
|
||||
{name: "green", current: false, url: "#Green"},
|
||||
{name: "blue", current: false, url: "#Blue"}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{../foo}}{{/each}}",
|
||||
mustache: "{{#names}}{{foo}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= @foo %><% end %>"
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}",
|
||||
mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>"
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
||||
dust: "{#person}{name}{age}{/person}",
|
||||
eco: "<%= @person.name %><%= @person.age %>",
|
||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
||||
},
|
||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
||||
},
|
||||
|
||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
||||
dust: "{#peeps}{>variables/}{/peeps}",
|
||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
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,7 @@
|
||||
module.exports = {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
};
|
||||
@@ -4,11 +4,11 @@ module.exports = {
|
||||
return 'foo ' + value;
|
||||
},
|
||||
header: function() {
|
||||
return 'Colors';
|
||||
return "Colors";
|
||||
}
|
||||
},
|
||||
handlebars: '{{echo (header)}}',
|
||||
eco: '<%= @echo(@header()) %>'
|
||||
handlebars: "{{echo (header)}}",
|
||||
eco: "<%= @echo(@header()) %>"
|
||||
};
|
||||
|
||||
module.exports.context = module.exports.helpers;
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
context: {name: "Mick", count: 30},
|
||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
||||
dust: "Hello {name}! You have {count} new messages.",
|
||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
||||
};
|
||||
|
||||
@@ -1,66 +1,52 @@
|
||||
var _ = require('underscore'),
|
||||
runner = require('./util/template-runner'),
|
||||
eco,
|
||||
dust,
|
||||
Handlebars,
|
||||
Mustache;
|
||||
runner = require('./util/template-runner'),
|
||||
templates = require('./templates'),
|
||||
|
||||
eco, dust, Handlebars, Mustache, eco;
|
||||
|
||||
try {
|
||||
dust = require('dustjs-linkedin');
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
dust = require("dustjs-linkedin");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require('mustache');
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
Mustache = require("mustache");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
eco = require('eco');
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
eco = require("eco");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
function error() {
|
||||
throw new Error('EWOT');
|
||||
throw new Error("EWOT");
|
||||
}
|
||||
|
||||
function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
// Create aliases to minimize any impact from having to walk up the closure tree.
|
||||
var templateName = name,
|
||||
context = template.context,
|
||||
partials = template.partials,
|
||||
handlebarsOut,
|
||||
compatOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, { data: false }),
|
||||
compat = Handlebars.compile(template.handlebars, {
|
||||
data: false,
|
||||
compat: true
|
||||
}),
|
||||
options = { helpers: template.helpers };
|
||||
_.each(template.partials && template.partials.handlebars, function(
|
||||
partial,
|
||||
partialName
|
||||
) {
|
||||
Handlebars.registerPartial(
|
||||
partialName,
|
||||
Handlebars.compile(partial, { data: false })
|
||||
);
|
||||
context = template.context,
|
||||
partials = template.partials,
|
||||
|
||||
handlebarsOut,
|
||||
compatOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, name) {
|
||||
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
|
||||
});
|
||||
|
||||
handlebarsOut = handlebar(context, options);
|
||||
bench('handlebars', function() {
|
||||
bench("handlebars", function() {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
compatOut = compat(context, options);
|
||||
bench('compat', function() {
|
||||
bench("compat", function() {
|
||||
compat(context, options);
|
||||
});
|
||||
|
||||
@@ -73,12 +59,10 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
dustOut = false;
|
||||
dust.loadSource(dust.compile(template.dust, templateName));
|
||||
|
||||
dust.render(templateName, context, function(err, out) {
|
||||
dustOut = out;
|
||||
});
|
||||
dust.render(templateName, context, function(err, out) { dustOut = out; });
|
||||
|
||||
bench('dust', function() {
|
||||
dust.render(templateName, context, function() {});
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
});
|
||||
} else {
|
||||
bench('dust', error);
|
||||
@@ -91,26 +75,26 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
|
||||
ecoOut = ecoTemplate(context);
|
||||
|
||||
bench('eco', function() {
|
||||
bench("eco", function() {
|
||||
ecoTemplate(context);
|
||||
});
|
||||
} else {
|
||||
bench('eco', error);
|
||||
bench("eco", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mustache) {
|
||||
var mustacheSource = template.mustache,
|
||||
mustachePartials = partials && partials.mustache;
|
||||
mustachePartials = partials && partials.mustache;
|
||||
|
||||
if (mustacheSource) {
|
||||
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
|
||||
bench('mustache', function() {
|
||||
bench("mustache", function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench('mustache', error);
|
||||
bench("mustache", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,16 +108,9 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
b = b.replace(/\s/g, '');
|
||||
|
||||
if (handlebarsOut !== b) {
|
||||
throw new Error(
|
||||
'Template output mismatch: ' +
|
||||
name +
|
||||
'\n\nHandlebars: ' +
|
||||
handlebarsOut +
|
||||
'\n\n' +
|
||||
lang +
|
||||
': ' +
|
||||
b
|
||||
);
|
||||
throw new Error('Template output mismatch: ' + name
|
||||
+ '\n\nHandlebars: ' + handlebarsOut
|
||||
+ '\n\n' + lang + ': ' + b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +122,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring load incase we are being run inline with the grunt build
|
||||
Handlebars = require('../../lib');
|
||||
Handlebars = require('../lib');
|
||||
|
||||
console.log('Execution Throughput');
|
||||
runner(grunt, makeSuite, function(times, scaled) {
|
||||
@@ -1,7 +1,7 @@
|
||||
var _ = require('underscore'),
|
||||
Benchmark = require('benchmark');
|
||||
Benchmark = require("benchmark");
|
||||
|
||||
function BenchWarmer() {
|
||||
var BenchWarmer = function(names) {
|
||||
this.benchmarks = [];
|
||||
this.currentBenches = [];
|
||||
this.names = [];
|
||||
@@ -9,7 +9,9 @@ function BenchWarmer() {
|
||||
this.minimum = Infinity;
|
||||
this.maximum = -Infinity;
|
||||
this.errors = {};
|
||||
}
|
||||
};
|
||||
|
||||
var print = require("sys").print;
|
||||
|
||||
BenchWarmer.prototype = {
|
||||
winners: function(benches) {
|
||||
@@ -27,25 +29,20 @@ BenchWarmer.prototype = {
|
||||
});
|
||||
},
|
||||
push: function(name, fn) {
|
||||
if (this.names.indexOf(name) === -1) {
|
||||
if(this.names.indexOf(name) == -1) {
|
||||
this.names.push(name);
|
||||
}
|
||||
|
||||
var first = this.first,
|
||||
suiteName = this.suiteName,
|
||||
self = this;
|
||||
var first = this.first, suiteName = this.suiteName, self = this;
|
||||
this.first = false;
|
||||
|
||||
var bench = new Benchmark(fn, {
|
||||
name: this.suiteName + ': ' + name,
|
||||
name: this.suiteName + ": " + name,
|
||||
onComplete: function() {
|
||||
if (first) {
|
||||
self.startLine(suiteName);
|
||||
}
|
||||
if(first) { self.startLine(suiteName); }
|
||||
self.writeBench(bench);
|
||||
self.currentBenches.push(bench);
|
||||
},
|
||||
onError: function() {
|
||||
}, onError: function() {
|
||||
self.errors[this.name] = this;
|
||||
}
|
||||
});
|
||||
@@ -61,13 +58,13 @@ BenchWarmer.prototype = {
|
||||
this.printHeader('ops/msec', true);
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: 'run',
|
||||
name: "run",
|
||||
onComplete: function() {
|
||||
self.scaleTimes();
|
||||
|
||||
self.startLine('');
|
||||
|
||||
console.log('\n');
|
||||
print('\n');
|
||||
self.printHeader('scaled');
|
||||
_.each(self.scaled, function(value, name) {
|
||||
self.startLine(name);
|
||||
@@ -76,77 +73,58 @@ BenchWarmer.prototype = {
|
||||
self.writeValue(value[lang] || '');
|
||||
});
|
||||
});
|
||||
console.log('\n');
|
||||
print('\n');
|
||||
|
||||
var errors = false,
|
||||
prop,
|
||||
bench;
|
||||
for (prop in self.errors) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(self, prop) &&
|
||||
self.errors[prop].error.message !== 'EWOT'
|
||||
) {
|
||||
var errors = false, prop, bench;
|
||||
for(prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
console.log('\n\nErrors:\n');
|
||||
Object.keys(self.errors).forEach(function(prop) {
|
||||
if (self.errors[prop].error.message !== 'EWOT') {
|
||||
if(errors) {
|
||||
print("\n\nErrors:\n");
|
||||
for(prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
bench = self.errors[prop];
|
||||
console.log('\n' + bench.name + ':\n');
|
||||
console.log(bench.error.message);
|
||||
if (bench.error.stack) {
|
||||
console.log(bench.error.stack.join('\n'));
|
||||
print("\n" + bench.name + ":\n");
|
||||
print(bench.error.message);
|
||||
if(bench.error.stack) {
|
||||
print(bench.error.stack.join("\n"));
|
||||
}
|
||||
console.log('\n');
|
||||
print("\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n');
|
||||
print("\n");
|
||||
},
|
||||
|
||||
scaleTimes: function() {
|
||||
var scaled = (this.scaled = {});
|
||||
_.each(
|
||||
this.times,
|
||||
function(times, name) {
|
||||
var output = (scaled[name] = {});
|
||||
var scaled = this.scaled = {};
|
||||
_.each(this.times, function(times, name) {
|
||||
var output = scaled[name] = {};
|
||||
|
||||
_.each(
|
||||
times,
|
||||
function(time, lang) {
|
||||
output[lang] = (
|
||||
((time - this.minimum) / (this.maximum - this.minimum)) *
|
||||
100
|
||||
).toFixed(2);
|
||||
},
|
||||
this
|
||||
);
|
||||
},
|
||||
this
|
||||
);
|
||||
_.each(times, function(time, lang) {
|
||||
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
printHeader: function(title, winners) {
|
||||
var benchSize = 0,
|
||||
names = this.names,
|
||||
i,
|
||||
l;
|
||||
var benchSize = 0, names = this.names, i, l;
|
||||
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
var name = names[i];
|
||||
|
||||
if (benchSize < name.length) {
|
||||
benchSize = name.length;
|
||||
}
|
||||
if(benchSize < name.length) { benchSize = name.length; }
|
||||
}
|
||||
|
||||
this.nameSize = benchSize + 2;
|
||||
@@ -155,30 +133,28 @@ BenchWarmer.prototype = {
|
||||
|
||||
this.startLine(title);
|
||||
horSize = horSize + this.benchSize;
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
this.writeValue(names[i]);
|
||||
horSize = horSize + this.benchSize;
|
||||
}
|
||||
|
||||
if (winners) {
|
||||
console.log('WINNER(S)');
|
||||
horSize = horSize + 'WINNER(S)'.length;
|
||||
print("WINNER(S)");
|
||||
horSize = horSize + "WINNER(S)".length;
|
||||
}
|
||||
|
||||
console.log('\n' + new Array(horSize + 1).join('-'));
|
||||
print("\n" + new Array(horSize + 1).join("-"));
|
||||
},
|
||||
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(
|
||||
bench
|
||||
) {
|
||||
return bench.name.split(': ')[1];
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
return bench.name.split(": ")[1];
|
||||
});
|
||||
|
||||
this.currentBenches = [];
|
||||
|
||||
console.log(winners.join(', '));
|
||||
console.log('\n');
|
||||
print(winners.join(", "));
|
||||
print("\n");
|
||||
|
||||
if (name) {
|
||||
this.writeValue(name);
|
||||
@@ -187,34 +163,36 @@ BenchWarmer.prototype = {
|
||||
writeBench: function(bench) {
|
||||
var out;
|
||||
|
||||
if (!bench.error) {
|
||||
if(!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = (count * bench.stats.rme) / 100,
|
||||
minimum,
|
||||
maximum;
|
||||
moe = count * bench.stats.rme / 100,
|
||||
minimum,
|
||||
maximum;
|
||||
|
||||
count = Math.round(count / 1000);
|
||||
moe = Math.round(moe / 1000);
|
||||
minimum = count - moe;
|
||||
maximum = count + moe;
|
||||
|
||||
out = count + ' ±' + moe + ' (' + bench.cycles + ')';
|
||||
out = count + " ±" + moe + " (" + bench.cycles + ")";
|
||||
|
||||
this.times[bench.suiteName][bench.benchName] = count;
|
||||
this.minimum = Math.min(this.minimum, minimum);
|
||||
this.maximum = Math.max(this.maximum, maximum);
|
||||
} else if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
out = 'E';
|
||||
if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
out = 'E';
|
||||
}
|
||||
}
|
||||
this.writeValue(out);
|
||||
},
|
||||
|
||||
writeValue: function(out) {
|
||||
var padding = this.benchSize - out.length + 1;
|
||||
out = out + new Array(padding).join(' ');
|
||||
console.log(out);
|
||||
out = out + new Array(padding).join(" ");
|
||||
print(out);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
var _ = require('underscore'),
|
||||
BenchWarmer = require('./benchwarmer'),
|
||||
templates = require('../templates');
|
||||
BenchWarmer = require('./benchwarmer'),
|
||||
templates = require('../templates');
|
||||
|
||||
module.exports = function(grunt, makeSuite, callback) {
|
||||
var warmer = new BenchWarmer();
|
||||
|
||||
var handlebarsOnly = grunt.option('handlebars-only'),
|
||||
grep = grunt.option('grep');
|
||||
grep = grunt.option('grep');
|
||||
if (grep) {
|
||||
grep = new RegExp(grep);
|
||||
}
|
||||
@@ -22,8 +22,6 @@ module.exports = function(grunt, makeSuite, callback) {
|
||||
});
|
||||
|
||||
warmer.bench(function() {
|
||||
if (callback) {
|
||||
callback(warmer.times, warmer.scaled);
|
||||
}
|
||||
callback && callback(warmer.times, warmer.scaled);
|
||||
});
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'no-var': 0
|
||||
}
|
||||
};
|
||||
+104
-169
@@ -1,176 +1,111 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var argv = parseArgs({
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'map': {
|
||||
'type': 'string',
|
||||
'description': 'Source Map File'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'c': {
|
||||
'type': 'string',
|
||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||
'alias': 'commonjs',
|
||||
'default': null
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
'alias': 'known'
|
||||
},
|
||||
'o': {
|
||||
'type': 'boolean',
|
||||
'description': 'Known helpers only',
|
||||
'alias': 'knownOnly'
|
||||
},
|
||||
'm': {
|
||||
'type': 'boolean',
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
'n': {
|
||||
'type': 'string',
|
||||
'description': 'Template namespace',
|
||||
'alias': 'namespace',
|
||||
'default': 'Handlebars.templates'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'N': {
|
||||
'type': 'string',
|
||||
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||
'alias': 'name'
|
||||
},
|
||||
'i': {
|
||||
'type': 'string',
|
||||
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
|
||||
'alias': 'string'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p': {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd': {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
},
|
||||
'e': {
|
||||
'type': 'string',
|
||||
'description': 'Template extension.',
|
||||
'alias': 'extension',
|
||||
'default': 'handlebars'
|
||||
},
|
||||
'b': {
|
||||
'type': 'boolean',
|
||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||
'alias': 'bom'
|
||||
},
|
||||
'v': {
|
||||
'type': 'boolean',
|
||||
'description': 'Prints the current compiler version',
|
||||
'alias': 'version'
|
||||
},
|
||||
'help': {
|
||||
'type': 'boolean',
|
||||
'description': 'Outputs this message'
|
||||
}
|
||||
});
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'map': {
|
||||
'type': 'string',
|
||||
'description': 'Source Map File'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'c': {
|
||||
'type': 'string',
|
||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||
'alias': 'commonjs',
|
||||
'default': null
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
'alias': 'known'
|
||||
},
|
||||
'o': {
|
||||
'type': 'boolean',
|
||||
'description': 'Known helpers only',
|
||||
'alias': 'knownOnly'
|
||||
},
|
||||
'm': {
|
||||
'type': 'boolean',
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
'n': {
|
||||
'type': 'string',
|
||||
'description': 'Template namespace',
|
||||
'alias': 'namespace',
|
||||
'default': 'Handlebars.templates'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
},
|
||||
'e': {
|
||||
'type': 'string',
|
||||
'description': 'Template extension.',
|
||||
'alias': 'extension',
|
||||
'default': 'handlebars'
|
||||
},
|
||||
'b': {
|
||||
'type': 'boolean',
|
||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||
'alias': 'bom'
|
||||
},
|
||||
'v': {
|
||||
'type': 'boolean',
|
||||
'description': 'Prints the current compiler version',
|
||||
'alias': 'version'
|
||||
},
|
||||
|
||||
argv.files = argv._;
|
||||
'help': {
|
||||
'type': 'boolean',
|
||||
'description': 'Outputs this message'
|
||||
}
|
||||
})
|
||||
|
||||
.check(function(argv) {
|
||||
if (argv.version) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var argv = optimist.argv;
|
||||
argv.templates = argv._;
|
||||
delete argv._;
|
||||
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||
printUsage(argv._spec, 120);
|
||||
} else {
|
||||
Precompiler.cli(opts);
|
||||
}
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
var str = '';
|
||||
while (str.length < n) {
|
||||
str += ' ';
|
||||
}
|
||||
return str;
|
||||
if (argv.help || (!argv.templates.length && !argv.version)) {
|
||||
optimist.showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
return require('../dist/cjs/precompiler').cli(argv);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.7.9",
|
||||
"version": "3.0.8",
|
||||
"main": "handlebars.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "https://handlebarsjs.com",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
@@ -11,9 +11,13 @@
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath"
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
|
||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.version = package["version"].sub "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>4.7.9</version>
|
||||
<version>3.0.8</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Extension of the Mustache logicless template language</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.7.9",
|
||||
"version": "3.0.8",
|
||||
"license": "MIT",
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
|
||||
+9
-99
@@ -16,34 +16,6 @@ var ast = Handlebars.parse(myTemplate);
|
||||
Handlebars.precompile(ast);
|
||||
```
|
||||
|
||||
### Parsing
|
||||
|
||||
There are two primary APIs that are used to parse an existing template into the AST:
|
||||
|
||||
#### parseWithoutProcessing
|
||||
|
||||
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||
```
|
||||
|
||||
#### parse
|
||||
|
||||
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||
|
||||
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||
|
||||
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
let ast = Handlebars.parse(myTemplate);
|
||||
```
|
||||
|
||||
### Basic
|
||||
|
||||
@@ -94,7 +66,7 @@ interface MustacheStatement <: Statement {
|
||||
|
||||
interface BlockStatement <: Statement {
|
||||
type: "BlockStatement";
|
||||
path: PathExpression | Literal;
|
||||
path: PathExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
@@ -111,23 +83,10 @@ interface PartialStatement <: Statement {
|
||||
name: PathExpression | SubExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
|
||||
indent: string;
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface PartialBlockStatement <: Statement {
|
||||
type: "PartialBlockStatement";
|
||||
name: PathExpression | SubExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
program: Program | null;
|
||||
|
||||
indent: string;
|
||||
openStrip: StripFlags | null;
|
||||
closeStrip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
|
||||
@@ -148,33 +107,6 @@ interface CommentStatement <: Statement {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```java
|
||||
interface Decorator <: Statement {
|
||||
type: "Decorator";
|
||||
|
||||
path: PathExpression | Literal;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface DecoratorBlock <: Statement {
|
||||
type: "DecoratorBlock";
|
||||
path: PathExpression | Literal;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
program: Program | null;
|
||||
|
||||
openStrip: StripFlags | null;
|
||||
closeStrip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
|
||||
|
||||
### Expressions
|
||||
|
||||
```java
|
||||
@@ -304,7 +236,7 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
|
||||
- `parent` is the existing code in the path resolution
|
||||
- `name` is the current path component
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
|
||||
|
||||
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
|
||||
|
||||
@@ -324,43 +256,21 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
- `initializeBuffer()`
|
||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||
|
||||
### Example for the compiler api.
|
||||
|
||||
This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change.
|
||||
|
||||
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
|
||||
|
||||
|
||||
```javascript
|
||||
function MyCompiler() {
|
||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||
}
|
||||
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
||||
MyCompiler.prototype = Object.create(Handlebars.JavaScriptCompiler);
|
||||
|
||||
// Use this compile to compile BlockStatment-Blocks
|
||||
MyCompiler.prototype.compiler = MyCompiler
|
||||
|
||||
MyCompiler.prototype.nameLookup = function(parent, name, type) {
|
||||
if (type === 'context') {
|
||||
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
|
||||
MyCompiler.nameLookup = function(parent, name, type) {
|
||||
if (type === 'partial') {
|
||||
return 'MyPartialList[' + JSON.stringify(name) ']';
|
||||
} else {
|
||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var env = Handlebars.create();
|
||||
env.registerHelper('lookupLowerCase', function(parent, name) {
|
||||
return parent[name.toLowerCase()]
|
||||
})
|
||||
|
||||
env.JavaScriptCompiler = MyCompiler;
|
||||
|
||||
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
||||
console.log(template({
|
||||
test: [
|
||||
{value: 'a'},
|
||||
{value: 'b'},
|
||||
{value: 'c'}
|
||||
]
|
||||
}));
|
||||
env.compile('my template');
|
||||
```
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Decorators
|
||||
|
||||
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to setup a particular state in the system prior to running the block.
|
||||
|
||||
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
|
||||
|
||||
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
|
||||
|
||||
- `program`: The block to wrap
|
||||
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
|
||||
- `container`: The current runtime container
|
||||
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
|
||||
- `data`: The current `@data` values
|
||||
- `blockParams`: The current block parameters stack
|
||||
- `depths`: The current context stack
|
||||
|
||||
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||
|
||||
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
// Handlebars should not use node or browser-specific APIs
|
||||
'shared-node-browser': true,
|
||||
node: false,
|
||||
browser: false
|
||||
}
|
||||
};
|
||||
+1
-6
@@ -2,11 +2,7 @@ import runtime from './handlebars.runtime';
|
||||
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import {
|
||||
parser as Parser,
|
||||
parse,
|
||||
parseWithoutProcessing
|
||||
} from './handlebars/compiler/base';
|
||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import Visitor from './handlebars/compiler/visitor';
|
||||
@@ -29,7 +25,6 @@ function create() {
|
||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||
hb.Parser = Parser;
|
||||
hb.parse = parse;
|
||||
hb.parseWithoutProcessing = parseWithoutProcessing;
|
||||
|
||||
return hb;
|
||||
}
|
||||
|
||||
+199
-43
@@ -1,13 +1,8 @@
|
||||
import { createFrame, extend, toString } from './utils';
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
import { registerDefaultHelpers } from './helpers';
|
||||
import { registerDefaultDecorators } from './decorators';
|
||||
import logger from './logger';
|
||||
import { resetLoggedProperties } from './internal/proto-access';
|
||||
|
||||
export const VERSION = '4.7.9';
|
||||
export const COMPILER_REVISION = 8;
|
||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||
export const VERSION = '3.0.1';
|
||||
export const COMPILER_REVISION = 6;
|
||||
|
||||
export const REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
@@ -15,34 +10,31 @@ export const REVISION_CHANGES = {
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1',
|
||||
7: '>= 4.0.0 <4.3.0',
|
||||
8: '>= 4.3.0'
|
||||
6: '>= 2.0.0-beta.1'
|
||||
};
|
||||
|
||||
const objectType = '[object Object]';
|
||||
const isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
export function HandlebarsEnvironment(helpers, partials, decorators) {
|
||||
export function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
this.decorators = decorators || {};
|
||||
|
||||
registerDefaultHelpers(this);
|
||||
registerDefaultDecorators(this);
|
||||
}
|
||||
|
||||
HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
logger: logger,
|
||||
log: logger.log,
|
||||
log: log,
|
||||
|
||||
registerHelper: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple helpers');
|
||||
}
|
||||
extend(this.helpers, name);
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
@@ -53,42 +45,206 @@ HandlebarsEnvironment.prototype = {
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
extend(this.partials, name);
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
if (typeof partial === 'undefined') {
|
||||
throw new Exception(
|
||||
`Attempting to register a partial called "${name}" as undefined`
|
||||
);
|
||||
throw new Exception('Attempting to register a partial as undefined');
|
||||
}
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
delete this.partials[name];
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
registerDecorator: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) {
|
||||
throw new Exception('Arg not supported with multiple decorators');
|
||||
}
|
||||
extend(this.decorators, name);
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
} else {
|
||||
this.decorators[name] = fn;
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
} else if (context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ret + fn(context[field], {
|
||||
data: data,
|
||||
blockParams: Utils.blockParams([context[field], field], [contextPath + field, null])
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(key)) {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
let fn = options.fn;
|
||||
|
||||
if (!Utils.isEmpty(context)) {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(message, options) {
|
||||
let level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, message);
|
||||
});
|
||||
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
if (!obj) {
|
||||
return obj;
|
||||
}
|
||||
if (Utils.dangerousPropertyRegex.test(String(field)) && !Object.prototype.hasOwnProperty.call(obj, field)) {
|
||||
return undefined;
|
||||
}
|
||||
return obj[field];
|
||||
});
|
||||
}
|
||||
|
||||
export let logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 1,
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, message) {
|
||||
if (typeof console !== 'undefined' && logger.level <= level) {
|
||||
let method = logger.methodMap[level];
|
||||
(console[method] || console.log).call(console, message); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
unregisterDecorator: function(name) {
|
||||
delete this.decorators[name];
|
||||
},
|
||||
/**
|
||||
* Reset the memory of illegal property accesses that have already been logged.
|
||||
* @deprecated should only be used in handlebars test-cases
|
||||
*/
|
||||
resetLoggedPropertyAccesses() {
|
||||
resetLoggedProperties();
|
||||
}
|
||||
};
|
||||
|
||||
export let log = logger.log;
|
||||
|
||||
export { createFrame, logger };
|
||||
export function createFrame(object) {
|
||||
let frame = Utils.extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
}
|
||||
|
||||
+130
-10
@@ -1,32 +1,152 @@
|
||||
let AST = {
|
||||
Program: function(statements, blockParams, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Program';
|
||||
this.body = statements;
|
||||
|
||||
this.blockParams = blockParams;
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
MustacheStatement: function(path, params, hash, escaped, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'MustacheStatement';
|
||||
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.escaped = escaped;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BlockStatement';
|
||||
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.openStrip = openStrip;
|
||||
this.inverseStrip = inverseStrip;
|
||||
this.closeStrip = closeStrip;
|
||||
},
|
||||
|
||||
PartialStatement: function(name, params, hash, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PartialStatement';
|
||||
|
||||
this.name = name;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
|
||||
this.indent = '';
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
ContentStatement: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'ContentStatement';
|
||||
this.original = this.value = string;
|
||||
},
|
||||
|
||||
CommentStatement: function(comment, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'CommentStatement';
|
||||
this.value = comment;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
SubExpression: function(path, params, hash, locInfo) {
|
||||
this.loc = locInfo;
|
||||
|
||||
this.type = 'SubExpression';
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
},
|
||||
|
||||
PathExpression: function(data, depth, parts, original, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PathExpression';
|
||||
|
||||
this.data = data;
|
||||
this.original = original;
|
||||
this.parts = parts;
|
||||
this.depth = depth;
|
||||
},
|
||||
|
||||
StringLiteral: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'StringLiteral';
|
||||
this.original =
|
||||
this.value = string;
|
||||
},
|
||||
|
||||
NumberLiteral: function(number, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NumberLiteral';
|
||||
this.original =
|
||||
this.value = Number(number);
|
||||
},
|
||||
|
||||
BooleanLiteral: function(bool, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BooleanLiteral';
|
||||
this.original =
|
||||
this.value = bool === 'true';
|
||||
},
|
||||
|
||||
UndefinedLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'UndefinedLiteral';
|
||||
this.original = this.value = undefined;
|
||||
},
|
||||
|
||||
NullLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NullLiteral';
|
||||
this.original = this.value = null;
|
||||
},
|
||||
|
||||
Hash: function(pairs, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Hash';
|
||||
this.pairs = pairs;
|
||||
},
|
||||
HashPair: function(key, value, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'HashPair';
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
},
|
||||
|
||||
// Public API used to evaluate derived attributes regarding AST nodes
|
||||
helpers: {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
helperExpression: function(node) {
|
||||
return (
|
||||
node.type === 'SubExpression' ||
|
||||
((node.type === 'MustacheStatement' ||
|
||||
node.type === 'BlockStatement') &&
|
||||
!!((node.params && node.params.length) || node.hash))
|
||||
);
|
||||
return !!(node.type === 'SubExpression' || node.params.length || node.hash);
|
||||
},
|
||||
|
||||
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
|
||||
// `..` or `this`.
|
||||
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 modify the object to operate properly.
|
||||
export default AST;
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import parser from './parser';
|
||||
import AST from './ast';
|
||||
import WhitespaceControl from './whitespace-control';
|
||||
import * as Helpers from './helpers';
|
||||
import Exception from '../exception';
|
||||
import { extend } from '../utils';
|
||||
|
||||
export { parser };
|
||||
|
||||
let yy = {};
|
||||
extend(yy, Helpers);
|
||||
extend(yy, Helpers, AST);
|
||||
|
||||
export function parseWithoutProcessing(input, options) {
|
||||
export function parse(input, options) {
|
||||
// Just return if an already-compiled AST was passed in.
|
||||
if (input.type === 'Program') {
|
||||
// When a pre-parsed AST is passed in, validate all node values to prevent
|
||||
// code injection via type-confused literals.
|
||||
validateInputAst(input);
|
||||
return input;
|
||||
}
|
||||
if (input.type === 'Program') { return input; }
|
||||
|
||||
parser.yy = yy;
|
||||
|
||||
@@ -25,77 +20,6 @@ export function parseWithoutProcessing(input, options) {
|
||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||
};
|
||||
|
||||
let ast = parser.parse(input);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function parse(input, options) {
|
||||
let ast = parseWithoutProcessing(input, options);
|
||||
let strip = new WhitespaceControl(options);
|
||||
|
||||
return strip.accept(ast);
|
||||
}
|
||||
|
||||
function validateInputAst(ast) {
|
||||
validateAstNode(ast);
|
||||
}
|
||||
|
||||
function validateAstNode(node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(validateAstNode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof node !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'PathExpression') {
|
||||
if (!isValidDepth(node.depth)) {
|
||||
throw new Exception(
|
||||
'Invalid AST: PathExpression.depth must be an integer'
|
||||
);
|
||||
}
|
||||
if (!Array.isArray(node.parts)) {
|
||||
throw new Exception('Invalid AST: PathExpression.parts must be an array');
|
||||
}
|
||||
for (let i = 0; i < node.parts.length; i++) {
|
||||
if (typeof node.parts[i] !== 'string') {
|
||||
throw new Exception(
|
||||
'Invalid AST: PathExpression.parts must only contain strings'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (node.type === 'NumberLiteral') {
|
||||
if (typeof node.value !== 'number' || !isFinite(node.value)) {
|
||||
throw new Exception('Invalid AST: NumberLiteral.value must be a number');
|
||||
}
|
||||
} else if (node.type === 'BooleanLiteral') {
|
||||
if (typeof node.value !== 'boolean') {
|
||||
throw new Exception(
|
||||
'Invalid AST: BooleanLiteral.value must be a boolean'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(node).forEach(propertyName => {
|
||||
if (propertyName === 'loc') {
|
||||
return;
|
||||
}
|
||||
validateAstNode(node[propertyName]);
|
||||
});
|
||||
}
|
||||
|
||||
function isValidDepth(depth) {
|
||||
return (
|
||||
typeof depth === 'number' &&
|
||||
isFinite(depth) &&
|
||||
Math.floor(depth) === depth &&
|
||||
depth >= 0
|
||||
);
|
||||
let strip = new WhitespaceControl();
|
||||
return strip.accept(parser.parse(input));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* global define, require */
|
||||
import { isArray } from '../utils';
|
||||
/*global define */
|
||||
import {isArray} from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
|
||||
try {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
// We don't support this in AMD environments. For these environments, we assume that
|
||||
// We don't support this in AMD environments. For these environments, we asusme that
|
||||
// they are running on the browser and thus have no need for the source-map library.
|
||||
let SourceMap = require('source-map');
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
@@ -38,7 +38,7 @@ if (!SourceNode) {
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function() {
|
||||
return { code: this.toString() };
|
||||
return {code: this.toString()};
|
||||
},
|
||||
toString: function() {
|
||||
return this.src;
|
||||
@@ -46,6 +46,7 @@ if (!SourceNode) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
let ret = [];
|
||||
@@ -61,15 +62,13 @@ function castChunk(chunk, codeGen, loc) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
||||
function CodeGen(srcFile) {
|
||||
this.srcFile = srcFile;
|
||||
this.source = [];
|
||||
}
|
||||
|
||||
CodeGen.prototype = {
|
||||
isEmpty() {
|
||||
return !this.source.length;
|
||||
},
|
||||
prepend: function(source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
@@ -91,23 +90,17 @@ CodeGen.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
empty: function() {
|
||||
let loc = this.currentLocation || { start: {} };
|
||||
empty: function(loc = this.currentLocation || {start: {}}) {
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
|
||||
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
|
||||
if (chunk instanceof SourceNode) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
chunk = castChunk(chunk, this, loc);
|
||||
|
||||
return new SourceNode(
|
||||
loc.start.line,
|
||||
loc.start.column,
|
||||
this.srcFile,
|
||||
chunk
|
||||
);
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
|
||||
},
|
||||
|
||||
functionCall: function(fn, type, params) {
|
||||
@@ -116,28 +109,26 @@ CodeGen.prototype = {
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return (
|
||||
'"' +
|
||||
(str + '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') +
|
||||
'"'
|
||||
);
|
||||
return '"' + (str + '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
let pairs = [];
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let ret = this.generateList(pairs);
|
||||
ret.prepend('{');
|
||||
@@ -145,22 +136,23 @@ CodeGen.prototype = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateList: function(entries) {
|
||||
let ret = this.empty();
|
||||
|
||||
generateList: function(entries, loc) {
|
||||
let ret = this.empty(loc);
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
if (i) {
|
||||
ret.add(',');
|
||||
}
|
||||
|
||||
ret.add(castChunk(entries[i], this));
|
||||
ret.add(castChunk(entries[i], this, loc));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries) {
|
||||
let ret = this.generateList(entries);
|
||||
generateArray: function(entries, loc) {
|
||||
let ret = this.generateList(entries, loc);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
@@ -169,3 +161,4 @@ CodeGen.prototype = {
|
||||
};
|
||||
|
||||
export default CodeGen;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import Exception from '../exception';
|
||||
import { isArray, indexOf, extend } from '../utils';
|
||||
import {isArray, indexOf} from '../utils';
|
||||
import AST from './ast';
|
||||
|
||||
const slice = [].slice;
|
||||
@@ -24,11 +22,8 @@ Compiler.prototype = {
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
let opcode = this.opcodes[i],
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (
|
||||
opcode.opcode !== otherOpcode.opcode ||
|
||||
!argEquals(opcode.args, otherOpcode.args)
|
||||
) {
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -57,28 +52,33 @@ Compiler.prototype = {
|
||||
|
||||
options.blockParams = options.blockParams || [];
|
||||
|
||||
options.knownHelpers = extend(
|
||||
Object.create(null),
|
||||
{
|
||||
helperMissing: true,
|
||||
blockHelperMissing: true,
|
||||
each: true,
|
||||
if: true,
|
||||
unless: true,
|
||||
with: true,
|
||||
log: true,
|
||||
lookup: true
|
||||
},
|
||||
options.knownHelpers
|
||||
);
|
||||
// These changes will propagate to the other compiler components
|
||||
let knownHelpers = options.knownHelpers;
|
||||
options.knownHelpers = {
|
||||
'helperMissing': true,
|
||||
'blockHelperMissing': true,
|
||||
'each': true,
|
||||
'if': true,
|
||||
'unless': true,
|
||||
'with': true,
|
||||
'log': true,
|
||||
'lookup': true
|
||||
};
|
||||
if (knownHelpers) {
|
||||
for (let name in knownHelpers) {
|
||||
if (name in knownHelpers) {
|
||||
options.knownHelpers[name] = knownHelpers[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.accept(program);
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
|
||||
this.usePartial = this.usePartial || result.usePartial;
|
||||
|
||||
@@ -89,11 +89,6 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[node.type]) {
|
||||
throw new Exception('Unknown type: ' + node.type, node);
|
||||
}
|
||||
|
||||
this.sourceNode.unshift(node);
|
||||
let ret = this[node.type](node);
|
||||
this.sourceNode.shift();
|
||||
@@ -104,7 +99,7 @@ Compiler.prototype = {
|
||||
this.options.blockParams.unshift(program.blockParams);
|
||||
|
||||
let body = program.body,
|
||||
bodyLength = body.length;
|
||||
bodyLength = body.length;
|
||||
for (let i = 0; i < bodyLength; i++) {
|
||||
this.accept(body[i]);
|
||||
}
|
||||
@@ -121,7 +116,7 @@ Compiler.prototype = {
|
||||
transformLiteralToPath(block);
|
||||
|
||||
let program = block.program,
|
||||
inverse = block.inverse;
|
||||
inverse = block.inverse;
|
||||
|
||||
program = program && this.compileProgram(program);
|
||||
inverse = inverse && this.compileProgram(inverse);
|
||||
@@ -153,44 +148,23 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
DecoratorBlock(decorator) {
|
||||
let program = decorator.program && this.compileProgram(decorator.program);
|
||||
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||
path = decorator.path;
|
||||
|
||||
this.useDecorators = true;
|
||||
this.opcode('registerDecorator', params.length, path.original);
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
let program = partial.program;
|
||||
if (program) {
|
||||
program = this.compileProgram(partial.program);
|
||||
}
|
||||
|
||||
let params = partial.params;
|
||||
if (params.length > 1) {
|
||||
throw new Exception(
|
||||
'Unsupported number of partial arguments: ' + params.length,
|
||||
partial
|
||||
);
|
||||
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
|
||||
} else if (!params.length) {
|
||||
if (this.options.explicitPartialContext) {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
} else {
|
||||
params.push({ type: 'PathExpression', parts: [], depth: 0 });
|
||||
}
|
||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
||||
}
|
||||
|
||||
let partialName = partial.name.original,
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
if (isDynamic) {
|
||||
this.accept(partial.name);
|
||||
}
|
||||
|
||||
this.setupFullMustacheParams(partial, program, undefined, true);
|
||||
this.setupFullMustacheParams(partial, undefined, undefined, true);
|
||||
|
||||
let indent = partial.indent || '';
|
||||
if (this.options.preventIndent && indent) {
|
||||
@@ -201,12 +175,9 @@ Compiler.prototype = {
|
||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||
this.opcode('append');
|
||||
},
|
||||
PartialBlockStatement: function(partialBlock) {
|
||||
this.PartialStatement(partialBlock);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
this.SubExpression(mustache);
|
||||
this.SubExpression(mustache); // eslint-disable-line new-cap
|
||||
|
||||
if (mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
@@ -214,9 +185,6 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
Decorator(decorator) {
|
||||
this.DecoratorBlock(decorator);
|
||||
},
|
||||
|
||||
ContentStatement: function(content) {
|
||||
if (content.value) {
|
||||
@@ -240,50 +208,38 @@ Compiler.prototype = {
|
||||
},
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
let path = sexpr.path,
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
let path = sexpr.path;
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
this.accept(sexpr.path);
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception(
|
||||
'You specified knownHelpersOnly, but used the unknown helper ' + name,
|
||||
sexpr
|
||||
);
|
||||
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
|
||||
} else {
|
||||
path.strict = true;
|
||||
path.falsy = true;
|
||||
|
||||
this.accept(path);
|
||||
this.opcode(
|
||||
'invokeHelper',
|
||||
params.length,
|
||||
path.original,
|
||||
AST.helpers.simpleId(path)
|
||||
);
|
||||
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -292,8 +248,8 @@ Compiler.prototype = {
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
let name = path.parts[0],
|
||||
scoped = AST.helpers.scopedId(path),
|
||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||
scoped = AST.helpers.scopedId(path),
|
||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||
|
||||
if (blockParamId) {
|
||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
||||
@@ -302,15 +258,9 @@ Compiler.prototype = {
|
||||
this.opcode('pushContext');
|
||||
} else if (path.data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
||||
this.opcode('lookupData', path.depth, path.parts);
|
||||
} else {
|
||||
this.opcode(
|
||||
'lookupOnContext',
|
||||
path.parts,
|
||||
path.falsy,
|
||||
path.strict,
|
||||
scoped
|
||||
);
|
||||
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -336,8 +286,8 @@ Compiler.prototype = {
|
||||
|
||||
Hash: function(hash) {
|
||||
let pairs = hash.pairs,
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
|
||||
this.opcode('pushHash');
|
||||
|
||||
@@ -352,11 +302,7 @@ Compiler.prototype = {
|
||||
|
||||
// HELPERS
|
||||
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) {
|
||||
@@ -385,7 +331,8 @@ Compiler.prototype = {
|
||||
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
||||
if (isEligible && !isHelper) {
|
||||
let name = sexpr.path.parts[0],
|
||||
options = this.options;
|
||||
options = this.options;
|
||||
|
||||
if (options.knownHelpers[name]) {
|
||||
isHelper = true;
|
||||
} else if (options.knownHelpersOnly) {
|
||||
@@ -413,7 +360,9 @@ Compiler.prototype = {
|
||||
|
||||
if (this.stringParams) {
|
||||
if (value.replace) {
|
||||
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||
value = value
|
||||
.replace(/^(\.?\.\/)*/g, '')
|
||||
.replace(/\//g, '.');
|
||||
}
|
||||
|
||||
if (val.depth) {
|
||||
@@ -431,7 +380,7 @@ Compiler.prototype = {
|
||||
if (this.trackIds) {
|
||||
let blockParamIndex;
|
||||
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||
}
|
||||
if (blockParamIndex) {
|
||||
let blockParamChild = val.parts.slice(1).join('.');
|
||||
@@ -440,9 +389,8 @@ Compiler.prototype = {
|
||||
value = val.original || value;
|
||||
if (value.replace) {
|
||||
value = value
|
||||
.replace(/^this(?:\.|$)/, '')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/^\.$/, '');
|
||||
.replace(/^\.\//g, '')
|
||||
.replace(/^\.$/g, '');
|
||||
}
|
||||
|
||||
this.opcode('pushId', val.type, value);
|
||||
@@ -469,13 +417,9 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
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],
|
||||
param = blockParams && indexOf(blockParams, name);
|
||||
param = blockParams && indexOf(blockParams, name);
|
||||
if (blockParams && param >= 0) {
|
||||
return [depth, param];
|
||||
}
|
||||
@@ -484,14 +428,8 @@ Compiler.prototype = {
|
||||
};
|
||||
|
||||
export function precompile(input, options, env) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
|
||||
input
|
||||
);
|
||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
@@ -503,22 +441,15 @@ export function precompile(input, options, env) {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export function compile(input, options = {}, env) {
|
||||
if (
|
||||
input == null ||
|
||||
(typeof input !== 'string' && input.type !== 'Program')
|
||||
) {
|
||||
throw new Exception(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||
input
|
||||
);
|
||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
|
||||
}
|
||||
|
||||
options = extend({}, options);
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
@@ -530,13 +461,8 @@ export function compile(input, options = {}, env) {
|
||||
|
||||
function compileInput() {
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
environment = new env.Compiler().compile(ast, options),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
return env.template(templateSpec);
|
||||
}
|
||||
|
||||
@@ -582,13 +508,6 @@ function transformLiteralToPath(sexpr) {
|
||||
let literal = sexpr.path;
|
||||
// Casting to string here to make false and 0 literal values play nicely with the rest
|
||||
// of the system.
|
||||
sexpr.path = {
|
||||
type: 'PathExpression',
|
||||
data: false,
|
||||
depth: 0,
|
||||
parts: [literal.original + ''],
|
||||
original: literal.original + '',
|
||||
loc: literal.loc
|
||||
};
|
||||
sexpr.path = new AST.PathExpression(false, 0, [literal.original + ''], literal.original + '', literal.loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
function validateClose(open, close) {
|
||||
close = close.path ? close.path.original : close;
|
||||
|
||||
if (open.path.original !== close) {
|
||||
let errorNode = { loc: open.path.loc };
|
||||
|
||||
throw new Exception(
|
||||
open.path.original + " doesn't match " + close,
|
||||
errorNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function SourceLocation(source, locInfo) {
|
||||
this.source = source;
|
||||
this.start = {
|
||||
@@ -27,7 +14,7 @@ export function SourceLocation(source, locInfo) {
|
||||
|
||||
export function id(token) {
|
||||
if (/^\[.*\]$/.test(token)) {
|
||||
return token.substring(1, token.length - 1);
|
||||
return token.substr(1, token.length - 2);
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
@@ -41,111 +28,79 @@ export function stripFlags(open, close) {
|
||||
}
|
||||
|
||||
export function stripComment(comment) {
|
||||
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
|
||||
return comment.replace(/^\{\{~?\!-?-?/, '')
|
||||
.replace(/-?-?~?\}\}$/, '');
|
||||
}
|
||||
|
||||
export function preparePath(data, parts, loc) {
|
||||
loc = this.locInfo(loc);
|
||||
export function preparePath(data, parts, locInfo) {
|
||||
locInfo = this.locInfo(locInfo);
|
||||
|
||||
let original = data ? '@' : '',
|
||||
dig = [],
|
||||
depth = 0;
|
||||
dig = [],
|
||||
depth = 0,
|
||||
depthString = '';
|
||||
|
||||
for (let i = 0, l = parts.length; i < l; i++) {
|
||||
let part = parts[i].part,
|
||||
// If we have [] syntax then we do not treat path references as operators,
|
||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||
isLiteral = parts[i].original !== part;
|
||||
// If we have [] syntax then we do not treat path references as operators,
|
||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||
isLiteral = parts[i].original !== part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||
if (dig.length > 0) {
|
||||
throw new Exception('Invalid path: ' + original, { loc });
|
||||
throw new Exception('Invalid path: ' + original, {loc: locInfo});
|
||||
} else if (part === '..') {
|
||||
depth++;
|
||||
depthString += '../';
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'PathExpression',
|
||||
data,
|
||||
depth,
|
||||
parts: dig,
|
||||
original,
|
||||
loc
|
||||
};
|
||||
return new this.PathExpression(data, depth, dig, original, locInfo);
|
||||
}
|
||||
|
||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
// Must use charAt to support IE pre-10
|
||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
let decorator = /\*/.test(open);
|
||||
return {
|
||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||
path,
|
||||
params,
|
||||
hash,
|
||||
escaped,
|
||||
strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
|
||||
}
|
||||
|
||||
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
||||
validateClose(openRawBlock, close);
|
||||
export function prepareRawBlock(openRawBlock, content, close, locInfo) {
|
||||
if (openRawBlock.path.original !== close) {
|
||||
let errorNode = {loc: openRawBlock.path.loc};
|
||||
|
||||
locInfo = this.locInfo(locInfo);
|
||||
let program = {
|
||||
type: 'Program',
|
||||
body: contents,
|
||||
strip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'BlockStatement',
|
||||
path: openRawBlock.path,
|
||||
params: openRawBlock.params,
|
||||
hash: openRawBlock.hash,
|
||||
program,
|
||||
openStrip: {},
|
||||
inverseStrip: {},
|
||||
closeStrip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareBlock(
|
||||
openBlock,
|
||||
program,
|
||||
inverseAndProgram,
|
||||
close,
|
||||
inverted,
|
||||
locInfo
|
||||
) {
|
||||
if (close && close.path) {
|
||||
validateClose(openBlock, close);
|
||||
throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
|
||||
}
|
||||
|
||||
let decorator = /\*/.test(openBlock.open);
|
||||
locInfo = this.locInfo(locInfo);
|
||||
let program = new this.Program([content], null, {}, locInfo);
|
||||
|
||||
return new this.BlockStatement(
|
||||
openRawBlock.path, openRawBlock.params, openRawBlock.hash,
|
||||
program, undefined,
|
||||
{}, {}, {},
|
||||
locInfo);
|
||||
}
|
||||
|
||||
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
|
||||
// When we are chaining inverse calls, we will not have a close path
|
||||
if (close && close.path && openBlock.path.original !== close.path.original) {
|
||||
let errorNode = {loc: openBlock.path.loc};
|
||||
|
||||
throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
|
||||
}
|
||||
|
||||
program.blockParams = openBlock.blockParams;
|
||||
|
||||
let inverse, inverseStrip;
|
||||
let inverse,
|
||||
inverseStrip;
|
||||
|
||||
if (inverseAndProgram) {
|
||||
if (decorator) {
|
||||
throw new Exception(
|
||||
'Unexpected inverse block on decorator',
|
||||
inverseAndProgram
|
||||
);
|
||||
}
|
||||
|
||||
if (inverseAndProgram.chain) {
|
||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||
}
|
||||
@@ -160,60 +115,9 @@ export function prepareBlock(
|
||||
program = inverted;
|
||||
}
|
||||
|
||||
return {
|
||||
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
|
||||
path: openBlock.path,
|
||||
params: openBlock.params,
|
||||
hash: openBlock.hash,
|
||||
program,
|
||||
inverse,
|
||||
openStrip: openBlock.strip,
|
||||
inverseStrip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareProgram(statements, loc) {
|
||||
if (!loc && statements.length) {
|
||||
const firstLoc = statements[0].loc,
|
||||
lastLoc = statements[statements.length - 1].loc;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (firstLoc && lastLoc) {
|
||||
loc = {
|
||||
source: firstLoc.source,
|
||||
start: {
|
||||
line: firstLoc.start.line,
|
||||
column: firstLoc.start.column
|
||||
},
|
||||
end: {
|
||||
line: lastLoc.end.line,
|
||||
column: lastLoc.end.column
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
body: statements,
|
||||
strip: {},
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
export function preparePartialBlock(open, program, close, locInfo) {
|
||||
validateClose(open, close);
|
||||
|
||||
return {
|
||||
type: 'PartialBlockStatement',
|
||||
name: open.path,
|
||||
params: open.params,
|
||||
hash: open.hash,
|
||||
program,
|
||||
openStrip: open.strip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
return new this.BlockStatement(
|
||||
openBlock.path, openBlock.params, openBlock.hash,
|
||||
program, inverse,
|
||||
openBlock.strip, inverseStrip, close && close.strip,
|
||||
this.locInfo(locInfo));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||
import Exception from '../exception';
|
||||
import { isArray } from '../utils';
|
||||
import {isArray, dangerousPropertyRegex} from '../utils';
|
||||
import CodeGen from './code-gen';
|
||||
|
||||
function Literal(value) {
|
||||
@@ -12,21 +12,28 @@ function JavaScriptCompiler() {}
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /*, type */) {
|
||||
return this.internalNameLookup(parent, name);
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
if (dangerousPropertyRegex.test(name)) {
|
||||
const isOwnProperty = [ this.aliasable('Object.prototype.hasOwnProperty'), '.call(', parent, ',', JSON.stringify(name), ')'];
|
||||
return ['(', isOwnProperty, '?', _actualLookup(), ' : undefined)'];
|
||||
}
|
||||
return _actualLookup();
|
||||
|
||||
function _actualLookup() {
|
||||
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
return [parent, '.', name];
|
||||
} else {
|
||||
return [parent, '[', JSON.stringify(name), ']'];
|
||||
}
|
||||
}
|
||||
},
|
||||
depthedLookup: function(name) {
|
||||
return [
|
||||
this.aliasable('container.lookup'),
|
||||
'(depths, ',
|
||||
JSON.stringify(name),
|
||||
')'
|
||||
];
|
||||
return [this.aliasable('this.lookup'), '(depths, "', name, '")'];
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
const revision = COMPILER_REVISION,
|
||||
versions = REVISION_CHANGES[revision];
|
||||
versions = REVISION_CHANGES[revision];
|
||||
return [revision, versions];
|
||||
},
|
||||
|
||||
@@ -54,12 +61,6 @@ JavaScriptCompiler.prototype = {
|
||||
return this.quotedString('');
|
||||
},
|
||||
// END PUBLIC API
|
||||
internalNameLookup: function(parent, name) {
|
||||
this.lookupPropertyFunctionIsUsed = true;
|
||||
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
|
||||
},
|
||||
|
||||
lookupPropertyFunctionIsUsed: false,
|
||||
|
||||
compile: function(environment, options, context, asObject) {
|
||||
this.environment = environment;
|
||||
@@ -71,7 +72,6 @@ JavaScriptCompiler.prototype = {
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
decorators: [],
|
||||
programs: [],
|
||||
environments: []
|
||||
};
|
||||
@@ -89,18 +89,14 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
this.useDepths =
|
||||
this.useDepths ||
|
||||
environment.useDepths ||
|
||||
environment.useDecorators ||
|
||||
this.options.compat;
|
||||
this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
|
||||
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
|
||||
|
||||
let opcodes = environment.opcodes,
|
||||
opcode,
|
||||
firstLoc,
|
||||
i,
|
||||
l;
|
||||
opcode,
|
||||
firstLoc,
|
||||
i,
|
||||
l;
|
||||
|
||||
for (i = 0, l = opcodes.length; i < l; i++) {
|
||||
opcode = opcodes[i];
|
||||
@@ -119,56 +115,16 @@ JavaScriptCompiler.prototype = {
|
||||
throw new Exception('Compile completed with content left on stack');
|
||||
}
|
||||
|
||||
if (!this.decorators.isEmpty()) {
|
||||
this.useDecorators = true;
|
||||
|
||||
this.decorators.prepend([
|
||||
'var decorators = container.decorators, ',
|
||||
this.lookupPropertyFunctionVarDeclaration(),
|
||||
';\n'
|
||||
]);
|
||||
this.decorators.push('return fn;');
|
||||
|
||||
if (asObject) {
|
||||
this.decorators = Function.apply(this, [
|
||||
'fn',
|
||||
'props',
|
||||
'container',
|
||||
'depth0',
|
||||
'data',
|
||||
'blockParams',
|
||||
'depths',
|
||||
this.decorators.merge()
|
||||
]);
|
||||
} else {
|
||||
this.decorators.prepend(
|
||||
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
|
||||
);
|
||||
this.decorators.push('}\n');
|
||||
this.decorators = this.decorators.merge();
|
||||
}
|
||||
} else {
|
||||
this.decorators = undefined;
|
||||
}
|
||||
|
||||
let fn = this.createFunctionContext(asObject);
|
||||
if (!this.isChild) {
|
||||
let ret = {
|
||||
compiler: this.compilerInfo(),
|
||||
main: fn
|
||||
};
|
||||
|
||||
if (this.decorators) {
|
||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
|
||||
let { programs, decorators } = this.context;
|
||||
let programs = this.context.programs;
|
||||
for (i = 0, l = programs.length; i < l; i++) {
|
||||
ret[i] = programs[i];
|
||||
if (decorators[i]) {
|
||||
ret[i + '_d'] = decorators[i];
|
||||
ret.useDecorators = true;
|
||||
if (programs[i]) {
|
||||
ret[i] = programs[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,11 +147,11 @@ JavaScriptCompiler.prototype = {
|
||||
if (!asObject) {
|
||||
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);
|
||||
|
||||
if (options.srcName) {
|
||||
ret = ret.toStringWithSourceMap({ file: options.destName });
|
||||
ret = ret.toStringWithSourceMap({file: options.destName});
|
||||
ret.map = ret.map && ret.map.toString();
|
||||
} else {
|
||||
ret = ret.toString();
|
||||
@@ -215,7 +171,6 @@ JavaScriptCompiler.prototype = {
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = new CodeGen(this.options.srcName);
|
||||
this.decorators = new CodeGen(this.options.srcName);
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
@@ -233,19 +188,16 @@ JavaScriptCompiler.prototype = {
|
||||
// aliases will not be used, but this case is already being run on the client and
|
||||
// we aren't concern about minimizing the template size.
|
||||
let aliasCount = 0;
|
||||
Object.keys(this.aliases).forEach(alias => {
|
||||
for (let alias in this.aliases) { // eslint-disable-line guard-for-in
|
||||
let node = this.aliases[alias];
|
||||
if (node.children && node.referenceCount > 1) {
|
||||
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
|
||||
|
||||
if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
|
||||
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
|
||||
node.children[0] = 'alias' + aliasCount;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.lookupPropertyFunctionIsUsed) {
|
||||
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
|
||||
}
|
||||
|
||||
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
||||
let params = ['depth0', 'helpers', 'partials', 'data'];
|
||||
|
||||
if (this.useBlockParams || this.useDepths) {
|
||||
params.push('blockParams');
|
||||
@@ -262,23 +214,18 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
return this.source.wrap([
|
||||
'function(',
|
||||
params.join(','),
|
||||
') {\n ',
|
||||
source,
|
||||
'}'
|
||||
]);
|
||||
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
|
||||
}
|
||||
},
|
||||
mergeSource: function(varDeclarations) {
|
||||
let isSimple = this.environment.isSimple,
|
||||
appendOnly = !this.forceBuffer,
|
||||
appendFirst,
|
||||
sourceSeen,
|
||||
bufferStart,
|
||||
bufferEnd;
|
||||
this.source.each(line => {
|
||||
appendOnly = !this.forceBuffer,
|
||||
appendFirst,
|
||||
|
||||
sourceSeen,
|
||||
bufferStart,
|
||||
bufferEnd;
|
||||
this.source.each((line) => {
|
||||
if (line.appendToBuffer) {
|
||||
if (bufferStart) {
|
||||
line.prepend(' + ');
|
||||
@@ -304,6 +251,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (appendOnly) {
|
||||
if (bufferStart) {
|
||||
bufferStart.prepend('return ');
|
||||
@@ -312,8 +260,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.source.push('return "";');
|
||||
}
|
||||
} else {
|
||||
varDeclarations +=
|
||||
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
|
||||
varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
|
||||
|
||||
if (bufferStart) {
|
||||
bufferStart.prepend('return buffer + ');
|
||||
@@ -324,25 +271,12 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
|
||||
if (varDeclarations) {
|
||||
this.source.prepend(
|
||||
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
|
||||
);
|
||||
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
|
||||
}
|
||||
|
||||
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]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, value
|
||||
@@ -353,10 +287,8 @@ JavaScriptCompiler.prototype = {
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function(name) {
|
||||
let blockHelperMissing = this.aliasable(
|
||||
'container.hooks.blockHelperMissing'
|
||||
),
|
||||
params = [this.contextName(0)];
|
||||
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
|
||||
params = [this.contextName(0)];
|
||||
this.setupHelperArgs(name, 0, params);
|
||||
|
||||
let blockName = this.popStack();
|
||||
@@ -373,10 +305,8 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after, if lastHelper: value
|
||||
ambiguousBlockValue: function() {
|
||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||
let blockHelperMissing = this.aliasable(
|
||||
'container.hooks.blockHelperMissing'
|
||||
),
|
||||
params = [this.contextName(0)];
|
||||
let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
|
||||
params = [this.contextName(0)];
|
||||
this.setupHelperArgs('', 0, params, true);
|
||||
|
||||
this.flushInline();
|
||||
@@ -385,14 +315,9 @@ JavaScriptCompiler.prototype = {
|
||||
params.splice(1, 0, current);
|
||||
|
||||
this.pushSource([
|
||||
'if (!',
|
||||
this.lastHelper,
|
||||
') { ',
|
||||
current,
|
||||
' = ',
|
||||
this.source.functionCall(blockHelperMissing, 'call', params),
|
||||
'}'
|
||||
]);
|
||||
'if (!', this.lastHelper, ') { ',
|
||||
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
|
||||
'}']);
|
||||
},
|
||||
|
||||
// [appendContent]
|
||||
@@ -422,24 +347,14 @@ JavaScriptCompiler.prototype = {
|
||||
// Otherwise, the empty string is appended
|
||||
append: function() {
|
||||
if (this.isInline()) {
|
||||
this.replaceStack(current => [' != null ? ', current, ' : ""']);
|
||||
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
|
||||
|
||||
this.pushSource(this.appendToBuffer(this.popStack()));
|
||||
} else {
|
||||
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) {
|
||||
this.pushSource([
|
||||
'else { ',
|
||||
this.appendToBuffer("''", undefined, true),
|
||||
' }'
|
||||
]);
|
||||
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -451,14 +366,8 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
this.pushSource(
|
||||
this.appendToBuffer([
|
||||
this.aliasable('container.escapeExpression'),
|
||||
'(',
|
||||
this.popStack(),
|
||||
')'
|
||||
])
|
||||
);
|
||||
this.pushSource(this.appendToBuffer(
|
||||
[this.aliasable('this.escapeExpression'), '(', this.popStack(), ')']));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
@@ -489,7 +398,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(parts, falsy, strict, scoped) {
|
||||
lookupOnContext: function(parts, falsy, scoped) {
|
||||
let i = 0;
|
||||
|
||||
if (!scoped && this.options.compat && !this.lastContext) {
|
||||
@@ -500,7 +409,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.pushContext();
|
||||
}
|
||||
|
||||
this.resolvePath('context', parts, i, falsy, strict);
|
||||
this.resolvePath('context', parts, i, falsy);
|
||||
},
|
||||
|
||||
// [lookupBlockParam]
|
||||
@@ -523,34 +432,26 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: data, ...
|
||||
//
|
||||
// Push the data lookup operator
|
||||
lookupData: function(depth, parts, strict) {
|
||||
lookupData: function(depth, parts) {
|
||||
if (!depth) {
|
||||
this.pushStackLiteral('data');
|
||||
} else {
|
||||
this.pushStackLiteral('container.data(data, ' + depth + ')');
|
||||
this.pushStackLiteral('this.data(data, ' + depth + ')');
|
||||
}
|
||||
|
||||
this.resolvePath('data', parts, 0, true, strict);
|
||||
this.resolvePath('data', parts, 0, true);
|
||||
},
|
||||
|
||||
resolvePath: function(type, parts, startPartIndex, falsy, strict) {
|
||||
resolvePath: function(type, parts, i, falsy) {
|
||||
if (this.options.strict || this.options.assumeObjects) {
|
||||
this.push(
|
||||
strictLookup(
|
||||
this.options.strict && strict,
|
||||
this,
|
||||
parts,
|
||||
startPartIndex,
|
||||
type
|
||||
)
|
||||
);
|
||||
this.push(strictLookup(this.options.strict, this, parts, type));
|
||||
return;
|
||||
}
|
||||
|
||||
let len = parts.length;
|
||||
for (let i = startPartIndex; i < len; i++) {
|
||||
/* eslint-disable no-loop-func */
|
||||
this.replaceStack(current => {
|
||||
for (; i < len; i++) {
|
||||
/*eslint-disable no-loop-func */
|
||||
this.replaceStack((current) => {
|
||||
let lookup = this.nameLookup(current, parts[i], type);
|
||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||
// needs to have the special handling for these values.
|
||||
@@ -561,7 +462,7 @@ JavaScriptCompiler.prototype = {
|
||||
return [' && ', lookup];
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-loop-func */
|
||||
/*eslint-enable no-loop-func */
|
||||
}
|
||||
},
|
||||
|
||||
@@ -573,14 +474,7 @@ JavaScriptCompiler.prototype = {
|
||||
// If the `value` is a lambda, replace it on the stack by
|
||||
// the return value of the lambda
|
||||
resolvePossibleLambda: function() {
|
||||
this.push([
|
||||
this.aliasable('container.lambda'),
|
||||
'(',
|
||||
this.popStack(),
|
||||
', ',
|
||||
this.contextName(0),
|
||||
')'
|
||||
]);
|
||||
this.push([this.aliasable('this.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
@@ -620,7 +514,7 @@ JavaScriptCompiler.prototype = {
|
||||
if (this.hash) {
|
||||
this.hashes.push(this.hash);
|
||||
}
|
||||
this.hash = { values: {}, types: [], contexts: [], ids: [] };
|
||||
this.hash = {values: [], types: [], contexts: [], ids: []};
|
||||
},
|
||||
popHash: function() {
|
||||
let hash = this.hash;
|
||||
@@ -675,38 +569,6 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
// [registerDecorator]
|
||||
//
|
||||
// On stack, before: hash, program, params..., ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Pops off the decorator's parameters, invokes the decorator,
|
||||
// and inserts the decorator into the decorators list.
|
||||
registerDecorator(paramSize, name) {
|
||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||
options = this.setupHelperArgs(name, paramSize);
|
||||
|
||||
// Store the resolved decorator in a variable and verify it is a function before
|
||||
// calling it. Without this, unregistered decorators can cause an unhandled TypeError
|
||||
// (calling undefined), which crashes the process — enabling Denial of Service.
|
||||
this.decorators.push(['var decorator = ', foundDecorator, ';']);
|
||||
this.decorators.push([
|
||||
'if (typeof decorator !== "function") { throw new Error(',
|
||||
this.quotedString('Missing decorator: "' + name + '"'),
|
||||
'); }'
|
||||
]);
|
||||
this.decorators.push([
|
||||
'fn = ',
|
||||
this.decorators.functionCall('decorator', '', [
|
||||
'fn',
|
||||
'props',
|
||||
'container',
|
||||
options
|
||||
]),
|
||||
' || fn;'
|
||||
]);
|
||||
},
|
||||
|
||||
// [invokeHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
@@ -718,43 +580,18 @@ JavaScriptCompiler.prototype = {
|
||||
// If the helper is not found, `helperMissing` is called.
|
||||
invokeHelper: function(paramSize, name, isSimple) {
|
||||
let nonHelper = this.popStack(),
|
||||
helper = this.setupHelper(paramSize, name);
|
||||
helper = this.setupHelper(paramSize, name),
|
||||
simple = isSimple ? [helper.name, ' || '] : '';
|
||||
|
||||
let possibleFunctionCalls = [];
|
||||
|
||||
if (isSimple) {
|
||||
// direct call to helper
|
||||
possibleFunctionCalls.push(helper.name);
|
||||
}
|
||||
// call a function from the input object
|
||||
possibleFunctionCalls.push(nonHelper);
|
||||
let lookup = ['('].concat(simple, nonHelper);
|
||||
if (!this.options.strict) {
|
||||
possibleFunctionCalls.push(
|
||||
this.aliasable('container.hooks.helperMissing')
|
||||
);
|
||||
lookup.push(' || ', this.aliasable('helpers.helperMissing'));
|
||||
}
|
||||
lookup.push(')');
|
||||
|
||||
let functionLookupCode = [
|
||||
'(',
|
||||
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
|
||||
')'
|
||||
];
|
||||
let functionCall = this.source.functionCall(
|
||||
functionLookupCode,
|
||||
'call',
|
||||
helper.callParams
|
||||
);
|
||||
this.push(functionCall);
|
||||
this.push(this.source.functionCall(lookup, 'call', helper.callParams));
|
||||
},
|
||||
|
||||
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]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
@@ -787,31 +624,22 @@ JavaScriptCompiler.prototype = {
|
||||
this.emptyHash();
|
||||
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, ')'];
|
||||
if (!this.options.strict) {
|
||||
lookup[0] = '(helper = ';
|
||||
lookup.push(
|
||||
' != null ? helper : ',
|
||||
this.aliasable('container.hooks.helperMissing')
|
||||
this.aliasable('helpers.helperMissing')
|
||||
);
|
||||
}
|
||||
|
||||
this.push([
|
||||
'(',
|
||||
lookup,
|
||||
helper.paramsInit ? ['),(', helper.paramsInit] : [],
|
||||
'),',
|
||||
'(typeof helper === ',
|
||||
this.aliasable('"function"'),
|
||||
' ? ',
|
||||
this.source.functionCall('helper', 'call', helper.callParams),
|
||||
' : helper))'
|
||||
'(', lookup,
|
||||
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
|
||||
'(typeof helper === ', this.aliasable('"function"'), ' ? ',
|
||||
this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -824,7 +652,7 @@ JavaScriptCompiler.prototype = {
|
||||
// and pushes the result of the invocation back.
|
||||
invokePartial: function(isDynamic, name, indent) {
|
||||
let params = [],
|
||||
options = this.setupParams(name, 1, params);
|
||||
options = this.setupParams(name, 1, params, false);
|
||||
|
||||
if (isDynamic) {
|
||||
name = this.popStack();
|
||||
@@ -836,7 +664,6 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
options.helpers = 'helpers';
|
||||
options.partials = 'partials';
|
||||
options.decorators = 'container.decorators';
|
||||
|
||||
if (!isDynamic) {
|
||||
params.unshift(this.nameLookup('partials', name, 'partial'));
|
||||
@@ -850,7 +677,7 @@ JavaScriptCompiler.prototype = {
|
||||
options = this.objectLiteral(options);
|
||||
params.push(options);
|
||||
|
||||
this.push(this.source.functionCall('container.invokePartial', '', params));
|
||||
this.push(this.source.functionCall('this.invokePartial', '', params));
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
@@ -861,9 +688,9 @@ JavaScriptCompiler.prototype = {
|
||||
// Pops a value off the stack and assigns it to the current hash
|
||||
assignToHash: function(key) {
|
||||
let value = this.popStack(),
|
||||
context,
|
||||
type,
|
||||
id;
|
||||
context,
|
||||
type,
|
||||
id;
|
||||
|
||||
if (this.trackIds) {
|
||||
id = this.popStack();
|
||||
@@ -889,13 +716,8 @@ JavaScriptCompiler.prototype = {
|
||||
pushId: function(type, name, child) {
|
||||
if (type === 'BlockParam') {
|
||||
this.pushStackLiteral(
|
||||
'blockParams[' +
|
||||
name[0] +
|
||||
'].path[' +
|
||||
name[1] +
|
||||
']' +
|
||||
(child ? ' + ' + JSON.stringify('.' + child) : '')
|
||||
);
|
||||
'blockParams[' + name[0] + '].path[' + name[1] + ']'
|
||||
+ (child ? ' + ' + JSON.stringify('.' + child) : ''));
|
||||
} else if (type === 'PathExpression') {
|
||||
this.pushString(name);
|
||||
} else if (type === 'SubExpression') {
|
||||
@@ -910,40 +732,30 @@ JavaScriptCompiler.prototype = {
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
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++) {
|
||||
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 index = this.matchExistingProgram(child);
|
||||
|
||||
if (existing == null) {
|
||||
// Placeholder to prevent name conflicts for nested children
|
||||
let index = this.context.programs.push('') - 1;
|
||||
if (index == null) {
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(
|
||||
child,
|
||||
options,
|
||||
this.context,
|
||||
!this.precompile
|
||||
);
|
||||
this.context.decorators[index] = compiler.decorators;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
|
||||
this.context.environments[index] = child;
|
||||
|
||||
this.useDepths = this.useDepths || compiler.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
|
||||
child.useDepths = this.useDepths;
|
||||
child.useBlockParams = this.useBlockParams;
|
||||
} else {
|
||||
child.index = existing.index;
|
||||
child.name = 'program' + existing.index;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
|
||||
this.useDepths = this.useDepths || existing.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || existing.useBlockParams;
|
||||
this.useDepths = this.useDepths || child.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || child.useBlockParams;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -951,14 +763,14 @@ JavaScriptCompiler.prototype = {
|
||||
for (let i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
let environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
return environment;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
let child = this.environment.children[guid],
|
||||
programParams = [child.index, 'data', child.blockParams];
|
||||
programParams = [child.index, 'data', child.blockParams];
|
||||
|
||||
if (this.useBlockParams || this.useDepths) {
|
||||
programParams.push('blockParams');
|
||||
@@ -967,7 +779,7 @@ JavaScriptCompiler.prototype = {
|
||||
programParams.push('depths');
|
||||
}
|
||||
|
||||
return 'container.program(' + programParams.join(', ') + ')';
|
||||
return 'this.program(' + programParams.join(', ') + ')';
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
@@ -993,11 +805,7 @@ JavaScriptCompiler.prototype = {
|
||||
pushSource: function(source) {
|
||||
if (this.pendingContent) {
|
||||
this.source.push(
|
||||
this.appendToBuffer(
|
||||
this.source.quotedString(this.pendingContent),
|
||||
this.pendingLocation
|
||||
)
|
||||
);
|
||||
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
|
||||
this.pendingContent = undefined;
|
||||
}
|
||||
|
||||
@@ -1008,9 +816,9 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
replaceStack: function(callback) {
|
||||
let prefix = ['('],
|
||||
stack,
|
||||
createdStack,
|
||||
usedLiteral;
|
||||
stack,
|
||||
createdStack,
|
||||
usedLiteral;
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!this.isInline()) {
|
||||
@@ -1047,9 +855,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
incrStack: function() {
|
||||
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();
|
||||
},
|
||||
topStackName: function() {
|
||||
@@ -1076,9 +882,9 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
popStack: function(wrapped) {
|
||||
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;
|
||||
} else {
|
||||
if (!inline) {
|
||||
@@ -1093,8 +899,8 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
||||
item = stack[stack.length - 1];
|
||||
let stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (item instanceof Literal) {
|
||||
@@ -1136,33 +942,19 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
setupHelper: function(paramSize, name, blockHelper) {
|
||||
let params = [],
|
||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
||||
callContext = this.aliasable(
|
||||
`${this.contextName(0)} != null ? ${this.contextName(
|
||||
0
|
||||
)} : (container.nullContext || {})`
|
||||
);
|
||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||
let foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: [callContext].concat(params)
|
||||
callParams: [this.contextName(0)].concat(params)
|
||||
};
|
||||
},
|
||||
|
||||
setupParams: function(helper, paramSize, params) {
|
||||
let options = {},
|
||||
contexts = [],
|
||||
types = [],
|
||||
ids = [],
|
||||
objectArgs = !params,
|
||||
param;
|
||||
|
||||
if (objectArgs) {
|
||||
params = [];
|
||||
}
|
||||
let options = {}, contexts = [], types = [], ids = [], param;
|
||||
|
||||
options.name = this.quotedString(helper);
|
||||
options.hash = this.popStack();
|
||||
@@ -1176,13 +968,13 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
|
||||
let inverse = this.popStack(),
|
||||
program = this.popStack();
|
||||
program = this.popStack();
|
||||
|
||||
// Avoid setting fn and inverse if neither are set. This allows
|
||||
// helpers to do a check for `if (options.fn)`
|
||||
if (program || inverse) {
|
||||
options.fn = program || 'container.noop';
|
||||
options.inverse = inverse || 'container.noop';
|
||||
options.fn = program || 'this.noop';
|
||||
options.inverse = inverse || 'this.noop';
|
||||
}
|
||||
|
||||
// The parameters go on to the stack in order (making sure that they are evaluated in order)
|
||||
@@ -1201,10 +993,6 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (objectArgs) {
|
||||
options.args = this.source.generateArray(params);
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = this.source.generateArray(ids);
|
||||
}
|
||||
@@ -1223,22 +1011,20 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
||||
let options = this.setupParams(helper, paramSize, params);
|
||||
options.loc = JSON.stringify(this.source.currentLocation);
|
||||
let options = this.setupParams(helper, paramSize, params, true);
|
||||
options = this.objectLiteral(options);
|
||||
if (useRegister) {
|
||||
this.useRegister('options');
|
||||
params.push('options');
|
||||
return ['options=', options];
|
||||
} else if (params) {
|
||||
} else {
|
||||
params.push(options);
|
||||
return '';
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
(function() {
|
||||
const reservedWords = (
|
||||
'break else new var' +
|
||||
@@ -1259,45 +1045,31 @@ JavaScriptCompiler.prototype = {
|
||||
' null true false'
|
||||
).split(' ');
|
||||
|
||||
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {});
|
||||
const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for (let i = 0, l = reservedWords.length; i < l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
})();
|
||||
}());
|
||||
|
||||
/**
|
||||
* @deprecated May be removed in the next major version
|
||||
*/
|
||||
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, startPartIndex, type) {
|
||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
||||
let stack = compiler.popStack(),
|
||||
len = parts.length;
|
||||
i = 0,
|
||||
len = parts.length;
|
||||
if (requireTerminal) {
|
||||
len--;
|
||||
}
|
||||
|
||||
for (let i = startPartIndex; i < len; i++) {
|
||||
for (; i < len; i++) {
|
||||
stack = compiler.nameLookup(stack, parts[i], type);
|
||||
}
|
||||
|
||||
if (requireTerminal) {
|
||||
return [
|
||||
compiler.aliasable('container.strict'),
|
||||
'(',
|
||||
stack,
|
||||
', ',
|
||||
compiler.quotedString(parts[len]),
|
||||
', ',
|
||||
JSON.stringify(compiler.source.currentLocation),
|
||||
' )'
|
||||
];
|
||||
return [compiler.aliasable('this.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
|
||||
} else {
|
||||
return stack;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable new-cap */
|
||||
/*eslint-disable new-cap */
|
||||
import Visitor from './visitor';
|
||||
|
||||
export function print(ast) {
|
||||
@@ -15,30 +15,29 @@ PrintVisitor.prototype.pad = function(string) {
|
||||
let out = '';
|
||||
|
||||
for (let i = 0, l = this.padding; i < l; i++) {
|
||||
out += ' ';
|
||||
out = out + ' ';
|
||||
}
|
||||
|
||||
out += string + '\n';
|
||||
out = out + string + '\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.Program = function(program) {
|
||||
let out = '',
|
||||
body = program.body,
|
||||
i,
|
||||
l;
|
||||
body = program.body,
|
||||
i, l;
|
||||
|
||||
if (program.blockParams) {
|
||||
let blockParams = 'BLOCK PARAMS: [';
|
||||
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
||||
blockParams += ' ' + program.blockParams[i];
|
||||
blockParams += ' ' + program.blockParams[i];
|
||||
}
|
||||
blockParams += ' ]';
|
||||
out += this.pad(blockParams);
|
||||
}
|
||||
|
||||
for (i = 0, l = body.length; i < l; i++) {
|
||||
out += this.accept(body[i]);
|
||||
out = out + this.accept(body[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
@@ -49,37 +48,26 @@ PrintVisitor.prototype.Program = function(program) {
|
||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.Decorator = function(mustache) {
|
||||
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
|
||||
block
|
||||
) {
|
||||
PrintVisitor.prototype.BlockStatement = function(block) {
|
||||
let out = '';
|
||||
|
||||
out += this.pad(
|
||||
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
|
||||
);
|
||||
out = out + this.pad('BLOCK:');
|
||||
this.padding++;
|
||||
out += this.pad(this.SubExpression(block));
|
||||
out = out + this.pad(this.SubExpression(block));
|
||||
if (block.program) {
|
||||
out += this.pad('PROGRAM:');
|
||||
out = out + this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
out += this.accept(block.program);
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) {
|
||||
this.padding++;
|
||||
}
|
||||
out += this.pad('{{^}}');
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad('{{^}}');
|
||||
this.padding++;
|
||||
out += this.accept(block.inverse);
|
||||
out = out + this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) {
|
||||
this.padding--;
|
||||
}
|
||||
if (block.program) { this.padding--; }
|
||||
}
|
||||
this.padding--;
|
||||
|
||||
@@ -96,22 +84,6 @@ PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||
}
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
|
||||
let content = 'PARTIAL BLOCK:' + partial.name.original;
|
||||
if (partial.params[0]) {
|
||||
content += ' ' + this.accept(partial.params[0]);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += ' ' + this.accept(partial.hash);
|
||||
}
|
||||
|
||||
content += ' ' + this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
content += this.accept(partial.program);
|
||||
this.padding--;
|
||||
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.ContentStatement = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.value + "' ]");
|
||||
@@ -123,8 +95,8 @@ PrintVisitor.prototype.CommentStatement = function(comment) {
|
||||
|
||||
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
||||
let params = sexpr.params,
|
||||
paramStrings = [],
|
||||
hash;
|
||||
paramStrings = [],
|
||||
hash;
|
||||
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
paramStrings.push(this.accept(params[i]));
|
||||
@@ -142,6 +114,7 @@ PrintVisitor.prototype.PathExpression = function(id) {
|
||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||
};
|
||||
|
||||
|
||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||
return '"' + string.value + '"';
|
||||
};
|
||||
@@ -164,7 +137,7 @@ PrintVisitor.prototype.NullLiteral = function() {
|
||||
|
||||
PrintVisitor.prototype.Hash = function(hash) {
|
||||
let pairs = hash.pairs,
|
||||
joinedPairs = [];
|
||||
joinedPairs = [];
|
||||
|
||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||
joinedPairs.push(this.accept(pairs[i]));
|
||||
@@ -175,4 +148,4 @@ PrintVisitor.prototype.Hash = function(hash) {
|
||||
PrintVisitor.prototype.HashPair = function(pair) {
|
||||
return pair.key + '=' + this.accept(pair.value);
|
||||
};
|
||||
/* eslint-enable new-cap */
|
||||
/*eslint-enable new-cap */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Exception from '../exception';
|
||||
import AST from './ast';
|
||||
|
||||
function Visitor() {
|
||||
this.parents = [];
|
||||
@@ -12,17 +13,9 @@ Visitor.prototype = {
|
||||
acceptKey: function(node, name) {
|
||||
let value = this.accept(node[name]);
|
||||
if (this.mutating) {
|
||||
// Hacky sanity check: This may have a few false positives for type for the helper
|
||||
// methods but will generally do the right thing without a lot of overhead.
|
||||
if (value && !Visitor.prototype[value.type]) {
|
||||
throw new Exception(
|
||||
'Unexpected node type "' +
|
||||
value.type +
|
||||
'" found when accepting ' +
|
||||
name +
|
||||
' on ' +
|
||||
node.type
|
||||
);
|
||||
// Hacky sanity check:
|
||||
if (value && (!value.type || !AST[value.type])) {
|
||||
throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
|
||||
}
|
||||
node[name] = value;
|
||||
}
|
||||
@@ -57,11 +50,6 @@ Visitor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[object.type]) {
|
||||
throw new Exception('Unknown type: ' + object.type, object);
|
||||
}
|
||||
|
||||
if (this.current) {
|
||||
this.parents.unshift(this.current);
|
||||
}
|
||||
@@ -82,23 +70,35 @@ Visitor.prototype = {
|
||||
this.acceptArray(program.body);
|
||||
},
|
||||
|
||||
MustacheStatement: visitSubExpression,
|
||||
Decorator: visitSubExpression,
|
||||
MustacheStatement: function(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
},
|
||||
|
||||
BlockStatement: visitBlock,
|
||||
DecoratorBlock: visitBlock,
|
||||
BlockStatement: function(block) {
|
||||
this.acceptRequired(block, 'path');
|
||||
this.acceptArray(block.params);
|
||||
this.acceptKey(block, 'hash');
|
||||
|
||||
PartialStatement: visitPartial,
|
||||
PartialBlockStatement: function(partial) {
|
||||
visitPartial.call(this, partial);
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
},
|
||||
|
||||
this.acceptKey(partial, 'program');
|
||||
PartialStatement: function(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
},
|
||||
|
||||
ContentStatement: function(/* content */) {},
|
||||
CommentStatement: function(/* comment */) {},
|
||||
|
||||
SubExpression: visitSubExpression,
|
||||
SubExpression: function(sexpr) {
|
||||
this.acceptRequired(sexpr, 'path');
|
||||
this.acceptArray(sexpr.params);
|
||||
this.acceptKey(sexpr, 'hash');
|
||||
},
|
||||
|
||||
PathExpression: function(/* path */) {},
|
||||
|
||||
@@ -116,21 +116,4 @@ Visitor.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function visitSubExpression(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
}
|
||||
function visitBlock(block) {
|
||||
visitSubExpression.call(this, block);
|
||||
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
}
|
||||
function visitPartial(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
}
|
||||
|
||||
export default Visitor;
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import Visitor from './visitor';
|
||||
|
||||
function WhitespaceControl(options = {}) {
|
||||
this.options = options;
|
||||
function WhitespaceControl() {
|
||||
}
|
||||
WhitespaceControl.prototype = new Visitor();
|
||||
|
||||
WhitespaceControl.prototype.Program = function(program) {
|
||||
const doStandalone = !this.options.ignoreStandalone;
|
||||
|
||||
let isRoot = !this.isRootSeen;
|
||||
this.isRootSeen = true;
|
||||
|
||||
let body = program.body;
|
||||
for (let i = 0, l = body.length; i < l; i++) {
|
||||
let current = body[i],
|
||||
strip = this.accept(current);
|
||||
strip = this.accept(current);
|
||||
|
||||
if (!strip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone =
|
||||
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
|
||||
if (strip.close) {
|
||||
omitRight(body, i, true);
|
||||
@@ -34,24 +31,24 @@ WhitespaceControl.prototype.Program = function(program) {
|
||||
omitLeft(body, i, true);
|
||||
}
|
||||
|
||||
if (doStandalone && inlineStandalone) {
|
||||
if (inlineStandalone) {
|
||||
omitRight(body, i);
|
||||
|
||||
if (omitLeft(body, i)) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if (current.type === 'PartialStatement') {
|
||||
// Pull out the whitespace from the final line
|
||||
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
|
||||
current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doStandalone && openStandalone) {
|
||||
if (openStandalone) {
|
||||
omitRight((current.program || current.inverse).body);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i);
|
||||
}
|
||||
if (doStandalone && closeStandalone) {
|
||||
if (closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(body, i);
|
||||
|
||||
@@ -61,18 +58,15 @@ WhitespaceControl.prototype.Program = function(program) {
|
||||
|
||||
return program;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
|
||||
block
|
||||
) {
|
||||
WhitespaceControl.prototype.BlockStatement = function(block) {
|
||||
this.accept(block.program);
|
||||
this.accept(block.inverse);
|
||||
|
||||
// Find the inverse program that is involed with whitespace stripping.
|
||||
let program = block.program || block.inverse,
|
||||
inverse = block.program && block.inverse,
|
||||
firstInverse = inverse,
|
||||
lastInverse = inverse;
|
||||
inverse = block.program && block.inverse,
|
||||
firstInverse = inverse,
|
||||
lastInverse = inverse;
|
||||
|
||||
if (inverse && inverse.chained) {
|
||||
firstInverse = inverse.body[0].program;
|
||||
@@ -112,11 +106,8 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
|
||||
}
|
||||
|
||||
// Find standalone else statments
|
||||
if (
|
||||
!this.options.ignoreStandalone &&
|
||||
isPrevWhitespace(program.body) &&
|
||||
isNextWhitespace(firstInverse.body)
|
||||
) {
|
||||
if (isPrevWhitespace(program.body)
|
||||
&& isNextWhitespace(firstInverse.body)) {
|
||||
omitLeft(program.body);
|
||||
omitRight(firstInverse.body);
|
||||
}
|
||||
@@ -127,15 +118,12 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
|
||||
return strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
|
||||
mustache
|
||||
) {
|
||||
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
|
||||
return mustache.strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
|
||||
node
|
||||
) {
|
||||
WhitespaceControl.prototype.PartialStatement =
|
||||
WhitespaceControl.prototype.CommentStatement = function(node) {
|
||||
/* istanbul ignore next */
|
||||
let strip = node.strip || {};
|
||||
return {
|
||||
@@ -145,6 +133,7 @@ WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.Comme
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function isPrevWhitespace(body, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = body.length;
|
||||
@@ -153,15 +142,13 @@ function isPrevWhitespace(body, i, isRoot) {
|
||||
// Nodes that end with newlines are considered whitespace (but are special
|
||||
// cased for strip operations)
|
||||
let prev = body[i - 1],
|
||||
sibling = body[i - 2];
|
||||
sibling = body[i - 2];
|
||||
if (!prev) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -170,15 +157,13 @@ function isNextWhitespace(body, i, isRoot) {
|
||||
}
|
||||
|
||||
let next = body[i + 1],
|
||||
sibling = body[i + 2];
|
||||
sibling = body[i + 2];
|
||||
if (!next) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,19 +176,12 @@ function isNextWhitespace(body, i, isRoot) {
|
||||
// content is met.
|
||||
function omitRight(body, i, multiple) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -216,17 +194,13 @@ function omitRight(body, i, multiple) {
|
||||
// content is met.
|
||||
function omitLeft(body, i, multiple) {
|
||||
let current = body[i == null ? body.length - 1 : i - 1];
|
||||
if (
|
||||
!current ||
|
||||
current.type !== 'ContentStatement' ||
|
||||
(!multiple && current.leftStripped)
|
||||
) {
|
||||
if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
||||
// We omit the last node if it's whitespace only and not preceeded by a non-content node.
|
||||
let original = current.value;
|
||||
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
|
||||
current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
|
||||
current.leftStripped = current.value !== original;
|
||||
return current.leftStripped;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import registerInline from './decorators/inline';
|
||||
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { extend } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function(context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
@@ -1,26 +1,13 @@
|
||||
const errorProps = [
|
||||
'description',
|
||||
'fileName',
|
||||
'lineNumber',
|
||||
'endLineNumber',
|
||||
'message',
|
||||
'name',
|
||||
'number',
|
||||
'stack'
|
||||
];
|
||||
|
||||
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
let loc = node && node.loc,
|
||||
line,
|
||||
endLineNumber,
|
||||
column,
|
||||
endColumn;
|
||||
|
||||
line,
|
||||
column;
|
||||
if (loc) {
|
||||
line = loc.start.line;
|
||||
endLineNumber = loc.end.line;
|
||||
column = loc.start.column;
|
||||
endColumn = loc.end.column;
|
||||
|
||||
message += ' - ' + line + ':' + column;
|
||||
}
|
||||
@@ -40,7 +27,6 @@ function Exception(message, node) {
|
||||
try {
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
this.endLineNumber = endLineNumber;
|
||||
|
||||
// Work around issue under safari where we can't directly set the column value
|
||||
/* istanbul ignore next */
|
||||
@@ -49,13 +35,8 @@ function Exception(message, node) {
|
||||
value: column,
|
||||
enumerable: true
|
||||
});
|
||||
Object.defineProperty(this, 'endColumn', {
|
||||
value: endColumn,
|
||||
enumerable: true
|
||||
});
|
||||
} else {
|
||||
this.column = column;
|
||||
this.endColumn = endColumn;
|
||||
}
|
||||
}
|
||||
} catch (nop) {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing';
|
||||
import registerEach from './helpers/each';
|
||||
import registerHelperMissing from './helpers/helper-missing';
|
||||
import registerIf from './helpers/if';
|
||||
import registerLog from './helpers/log';
|
||||
import registerLookup from './helpers/lookup';
|
||||
import registerWith from './helpers/with';
|
||||
|
||||
export function registerDefaultHelpers(instance) {
|
||||
registerBlockHelperMissing(instance);
|
||||
registerEach(instance);
|
||||
registerHelperMissing(instance);
|
||||
registerIf(instance);
|
||||
registerLog(instance);
|
||||
registerLookup(instance);
|
||||
registerWith(instance);
|
||||
}
|
||||
|
||||
export function moveHelperToHooks(instance, helperName, keepHelper) {
|
||||
if (instance.helpers[helperName]) {
|
||||
instance.hooks[helperName] = instance.helpers[helperName];
|
||||
if (!keepHelper) {
|
||||
// Using delete is slow
|
||||
instance.helpers[helperName] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { appendContextPath, createFrame, isArray } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
} else if (context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(
|
||||
options.data.contextPath,
|
||||
options.name
|
||||
);
|
||||
options = { data: data };
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import {
|
||||
appendContextPath,
|
||||
blockParams,
|
||||
createFrame,
|
||||
isArray,
|
||||
isFunction
|
||||
} from '../utils';
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath =
|
||||
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) {
|
||||
context = context.call(this);
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret =
|
||||
ret +
|
||||
fn(context[field], {
|
||||
data: data,
|
||||
blockParams: blockParams(
|
||||
[context[field], field],
|
||||
[contextPath + field, null]
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
if (i in context) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
}
|
||||
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||
const newContext = [];
|
||||
const iterator = context[Symbol.iterator]();
|
||||
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||
newContext.push(it.value);
|
||||
}
|
||||
context = newContext;
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
Object.keys(context).forEach(key => {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
});
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} construct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception(
|
||||
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#if requires exactly one argument');
|
||||
}
|
||||
if (isFunction(conditional)) {
|
||||
conditional = conditional.call(this);
|
||||
}
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
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,19 +0,0 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('log', function(/* message, options */) {
|
||||
let args = [undefined],
|
||||
options = arguments[arguments.length - 1];
|
||||
for (let i = 0; i < arguments.length - 1; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
|
||||
let level = 1;
|
||||
if (options.hash.level != null) {
|
||||
level = options.hash.level;
|
||||
} else if (options.data && options.data.level != null) {
|
||||
level = options.data.level;
|
||||
}
|
||||
args[0] = level;
|
||||
|
||||
instance.log(...args);
|
||||
});
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('lookup', function(obj, field, options) {
|
||||
if (!obj) {
|
||||
// Note for 5.0: Change to "obj == null" in 5.0
|
||||
return obj;
|
||||
}
|
||||
return options.lookupProperty(obj, field);
|
||||
});
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import {
|
||||
appendContextPath,
|
||||
blockParams,
|
||||
createFrame,
|
||||
isEmpty,
|
||||
isFunction
|
||||
} from '../utils';
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (arguments.length != 2) {
|
||||
throw new Exception('#with requires exactly one argument');
|
||||
}
|
||||
if (isFunction(context)) {
|
||||
context = context.call(this);
|
||||
}
|
||||
|
||||
let fn = options.fn;
|
||||
|
||||
if (!isEmpty(context)) {
|
||||
let data = options.data;
|
||||
if (options.data && options.ids) {
|
||||
data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(
|
||||
options.data.contextPath,
|
||||
options.ids[0]
|
||||
);
|
||||
}
|
||||
|
||||
return fn(context, {
|
||||
data: data,
|
||||
blockParams: blockParams([context], [data && data.contextPath])
|
||||
});
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { extend } from '../utils';
|
||||
import logger from '../logger';
|
||||
|
||||
const loggedProperties = Object.create(null);
|
||||
|
||||
export function createProtoAccessControl(runtimeOptions) {
|
||||
// Create an object with "null"-prototype to avoid truthy results on
|
||||
// prototype properties.
|
||||
const propertyWhiteList = Object.create(null);
|
||||
// eslint-disable-next-line no-proto
|
||||
propertyWhiteList['__proto__'] = false;
|
||||
extend(propertyWhiteList, runtimeOptions.allowedProtoProperties);
|
||||
|
||||
const methodWhiteList = Object.create(null);
|
||||
methodWhiteList['constructor'] = false;
|
||||
methodWhiteList['__defineGetter__'] = false;
|
||||
methodWhiteList['__defineSetter__'] = false;
|
||||
methodWhiteList['__lookupGetter__'] = false;
|
||||
methodWhiteList['__lookupSetter__'] = false;
|
||||
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
|
||||
|
||||
return {
|
||||
properties: {
|
||||
whitelist: propertyWhiteList,
|
||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||
},
|
||||
methods: {
|
||||
whitelist: methodWhiteList,
|
||||
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];
|
||||
});
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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,39 +0,0 @@
|
||||
import { indexOf } from './utils';
|
||||
|
||||
let logger = {
|
||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||
level: 'info',
|
||||
|
||||
// Maps a given level value to the `methodMap` indexes above.
|
||||
lookupLevel: function(level) {
|
||||
if (typeof level === 'string') {
|
||||
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||
if (levelMap >= 0) {
|
||||
level = levelMap;
|
||||
} else {
|
||||
level = parseInt(level, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
},
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, ...message) {
|
||||
level = logger.lookupLevel(level);
|
||||
|
||||
if (
|
||||
typeof console !== 'undefined' &&
|
||||
logger.lookupLevel(logger.level) <= level
|
||||
) {
|
||||
let method = logger.methodMap[level];
|
||||
// eslint-disable-next-line no-console
|
||||
if (!console[method]) {
|
||||
method = 'log';
|
||||
}
|
||||
console[method](...message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default logger;
|
||||
@@ -1,23 +1,12 @@
|
||||
/* global globalThis */
|
||||
/*global window */
|
||||
export default function(Handlebars) {
|
||||
/* istanbul ignore next */
|
||||
// https://mathiasbynens.be/notes/globalthis
|
||||
(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;
|
||||
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
$Handlebars = root.Handlebars;
|
||||
/* istanbul ignore next */
|
||||
Handlebars.noConflict = function() {
|
||||
if (globalThis.Handlebars === Handlebars) {
|
||||
globalThis.Handlebars = $Handlebars;
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
}
|
||||
return Handlebars;
|
||||
};
|
||||
}
|
||||
|
||||
+45
-285
@@ -1,48 +1,22 @@
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
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';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
if (
|
||||
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
|
||||
compilerRevision <= COMPILER_REVISION
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception(
|
||||
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' +
|
||||
runtimeVersions +
|
||||
') or downgrade your runtime to an older version (' +
|
||||
compilerVersions +
|
||||
').'
|
||||
);
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception(
|
||||
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' +
|
||||
compilerInfo[1] +
|
||||
').'
|
||||
);
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,36 +29,20 @@ export function template(templateSpec, env) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
templateSpec.main.decorator = templateSpec.main_d;
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as pseudo-supported APIs.
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
|
||||
const templateWasPrecompiledWithCompilerV7 =
|
||||
templateSpec.compiler && templateSpec.compiler[0] === 7;
|
||||
|
||||
function invokePartialWrapper(partial, context, options) {
|
||||
if (options.hash) {
|
||||
context = Utils.extend({}, context, options.hash);
|
||||
if (options.ids) {
|
||||
options.ids[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
|
||||
options.hooks = this.hooks;
|
||||
options.protoAccessControl = this.protoAccessControl;
|
||||
|
||||
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
options.partials[options.name] = env.compile(
|
||||
partial,
|
||||
templateSpec.compilerOptions,
|
||||
env
|
||||
);
|
||||
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
||||
result = options.partials[options.name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
@@ -101,44 +59,23 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception(
|
||||
'The partial ' +
|
||||
options.name +
|
||||
' could not be compiled when running in runtime-only mode'
|
||||
);
|
||||
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
|
||||
}
|
||||
}
|
||||
|
||||
// Just add water
|
||||
let container = {
|
||||
strict: function(obj, name, loc) {
|
||||
if (!obj || !(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||
loc: loc
|
||||
});
|
||||
strict: function(obj, name) {
|
||||
if (!(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
||||
}
|
||||
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;
|
||||
return obj[name];
|
||||
},
|
||||
lookup: function(depths, name) {
|
||||
const len = depths.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||
if (result != null) {
|
||||
return result;
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -150,25 +87,15 @@ export function template(templateSpec, env) {
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
return templateSpec[i];
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||
let programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
fn = this.fn(i);
|
||||
if (data || depths || blockParams || declaredBlockParams) {
|
||||
programWrapper = wrapProgram(
|
||||
this,
|
||||
i,
|
||||
fn,
|
||||
data,
|
||||
declaredBlockParams,
|
||||
blockParams,
|
||||
depths
|
||||
);
|
||||
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||
}
|
||||
@@ -181,17 +108,15 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return value;
|
||||
},
|
||||
mergeIfNeeded: function(param, common) {
|
||||
merge: function(param, common) {
|
||||
let obj = param || common;
|
||||
|
||||
if (param && common && param !== common) {
|
||||
if (param && common && (param !== common)) {
|
||||
obj = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
// An empty object to use as replacement for null-contexts
|
||||
nullContext: Object.seal({}),
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
@@ -205,82 +130,25 @@ export function template(templateSpec, env) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
let depths,
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
if (templateSpec.useDepths) {
|
||||
if (options.depths) {
|
||||
depths =
|
||||
context != options.depths[0]
|
||||
? [context].concat(options.depths)
|
||||
: options.depths;
|
||||
} else {
|
||||
depths = [context];
|
||||
}
|
||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
||||
}
|
||||
|
||||
function main(context /*, options*/) {
|
||||
return (
|
||||
'' +
|
||||
templateSpec.main(
|
||||
container,
|
||||
context,
|
||||
container.helpers,
|
||||
container.partials,
|
||||
data,
|
||||
blockParams,
|
||||
depths
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
main = executeDecorators(
|
||||
templateSpec.main,
|
||||
main,
|
||||
container,
|
||||
options.depths || [],
|
||||
data,
|
||||
blockParams
|
||||
);
|
||||
return main(context, options);
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
}
|
||||
|
||||
ret.isTop = true;
|
||||
|
||||
ret._setup = function(options) {
|
||||
if (!options.partial) {
|
||||
let mergedHelpers = {};
|
||||
addHelpers(mergedHelpers, env.helpers, container);
|
||||
addHelpers(mergedHelpers, options.helpers, container);
|
||||
container.helpers = mergedHelpers;
|
||||
container.helpers = container.merge(options.helpers, env.helpers);
|
||||
|
||||
if (templateSpec.usePartial) {
|
||||
// Use mergeIfNeeded here to prevent compiling global partials multiple times
|
||||
container.partials = container.mergeIfNeeded(
|
||||
options.partials,
|
||||
env.partials
|
||||
);
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
}
|
||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||
container.decorators = Utils.extend(
|
||||
{},
|
||||
env.decorators,
|
||||
options.decorators
|
||||
);
|
||||
}
|
||||
|
||||
container.hooks = {};
|
||||
container.protoAccessControl = createProtoAccessControl(options);
|
||||
|
||||
let keepHelperInHelpers =
|
||||
options.allowCallsToHelperMissing ||
|
||||
templateWasPrecompiledWithCompilerV7;
|
||||
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
||||
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
||||
} else {
|
||||
container.protoAccessControl = options.protoAccessControl; // internal option
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
container.decorators = options.decorators;
|
||||
container.hooks = options.hooks;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,106 +160,39 @@ export function template(templateSpec, env) {
|
||||
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;
|
||||
}
|
||||
|
||||
export function wrapProgram(
|
||||
container,
|
||||
i,
|
||||
fn,
|
||||
data,
|
||||
declaredBlockParams,
|
||||
blockParams,
|
||||
depths
|
||||
) {
|
||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
||||
function prog(context, options = {}) {
|
||||
let currentDepths = depths;
|
||||
if (
|
||||
depths &&
|
||||
context != depths[0] &&
|
||||
!(context === container.nullContext && depths[0] === null)
|
||||
) {
|
||||
currentDepths = [context].concat(depths);
|
||||
}
|
||||
|
||||
return fn(
|
||||
container,
|
||||
context,
|
||||
container.helpers,
|
||||
container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
currentDepths
|
||||
);
|
||||
return fn.call(container,
|
||||
context,
|
||||
container.helpers, container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
depths && [context].concat(depths));
|
||||
}
|
||||
|
||||
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
||||
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
prog.blockParams = declaredBlockParams || 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is currently part of the official API, therefore implementation details should not be changed.
|
||||
*/
|
||||
export function resolvePartial(partial, context, options) {
|
||||
if (!partial) {
|
||||
if (options.name === '@partial-block') {
|
||||
partial = lookupOwnProperty(options.data, 'partial-block');
|
||||
} else {
|
||||
partial = lookupOwnProperty(options.partials, options.name);
|
||||
}
|
||||
partial = options.partials[options.name];
|
||||
} else if (!partial.call && !options.name) {
|
||||
// This is a dynamic partial that returned a string
|
||||
options.name = partial;
|
||||
partial = lookupOwnProperty(options.partials, partial);
|
||||
partial = options.partials[partial];
|
||||
}
|
||||
return partial;
|
||||
}
|
||||
|
||||
export function invokePartial(partial, context, options) {
|
||||
// Use the current closure context to save the partial-block if this partial
|
||||
const currentPartialBlock = lookupOwnProperty(options.data, 'partial-block');
|
||||
options.partial = true;
|
||||
if (options.ids) {
|
||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||
}
|
||||
|
||||
let partialBlock;
|
||||
if (options.fn && options.fn !== noop) {
|
||||
options.data = createFrame(options.data);
|
||||
// Wrapper function to get access to currentPartialBlock from the closure
|
||||
let fn = options.fn;
|
||||
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
|
||||
context,
|
||||
options = {}
|
||||
) {
|
||||
// Restore the partial-block from the closure for the execution of the block
|
||||
// i.e. the part inside the block of the partial call.
|
||||
options.data = createFrame(options.data);
|
||||
options.data['partial-block'] = currentPartialBlock;
|
||||
return fn(context, options);
|
||||
};
|
||||
if (fn.partials) {
|
||||
options.partials = Utils.extend({}, options.partials, fn.partials);
|
||||
}
|
||||
}
|
||||
|
||||
if (partial === undefined && partialBlock) {
|
||||
partial = partialBlock;
|
||||
}
|
||||
|
||||
if (partial === undefined) {
|
||||
throw new Exception('The partial ' + options.name + ' could not be found');
|
||||
@@ -400,15 +201,7 @@ export function invokePartial(partial, context, options) {
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function lookupOwnProperty(obj, name) {
|
||||
if (obj && Object.prototype.hasOwnProperty.call(obj, name)) {
|
||||
return obj[name];
|
||||
}
|
||||
}
|
||||
export function noop() { return ''; }
|
||||
|
||||
function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
@@ -417,36 +210,3 @@ function initData(context, data) {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||
if (fn.decorator) {
|
||||
let props = {};
|
||||
prog = fn.decorator(
|
||||
prog,
|
||||
props,
|
||||
container,
|
||||
depths && depths[0],
|
||||
data,
|
||||
blockParams,
|
||||
depths
|
||||
);
|
||||
Utils.extend(prog, props);
|
||||
}
|
||||
return prog;
|
||||
}
|
||||
|
||||
function addHelpers(mergedHelpers, helpers, container) {
|
||||
if (!helpers) return;
|
||||
Object.keys(helpers).forEach(helperName => {
|
||||
let helper = helpers[helperName];
|
||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||
});
|
||||
}
|
||||
|
||||
function passLookupPropertyOption(helper, container) {
|
||||
const lookupProperty = container.lookupProperty;
|
||||
return wrapHelper(helper, options => {
|
||||
options.lookupProperty = lookupProperty;
|
||||
return options;
|
||||
});
|
||||
}
|
||||
|
||||
+17
-28
@@ -4,12 +4,13 @@ const escape = {
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
'`': '`'
|
||||
// The "equals-sign" is intentionally excluded from this list
|
||||
// due to semantic-versioning issues (see #1489)
|
||||
};
|
||||
|
||||
const badChars = /[&<>"'`=]/g,
|
||||
possible = /[&<>"'`=]/;
|
||||
const badChars = /[&<>"'`]/g,
|
||||
possible = /[&<>"'`]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr];
|
||||
@@ -31,31 +32,24 @@ export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/* eslint-disable func-style */
|
||||
let isFunction = function(value) {
|
||||
/*eslint-disable func-style, no-var */
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return (
|
||||
typeof value === 'function' &&
|
||||
toString.call(value) === '[object Function]'
|
||||
);
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export { isFunction };
|
||||
/* eslint-enable func-style */
|
||||
export var isFunction;
|
||||
/*eslint-enable func-style, no-var */
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray =
|
||||
Array.isArray ||
|
||||
function(value) {
|
||||
return value && typeof value === 'object'
|
||||
? toString.call(value) === '[object Array]'
|
||||
: false;
|
||||
};
|
||||
export const isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
|
||||
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||
export function indexOf(array, value) {
|
||||
@@ -67,6 +61,7 @@ export function indexOf(array, value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
export function escapeExpression(string) {
|
||||
if (typeof string !== 'string') {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
@@ -84,9 +79,7 @@ export function escapeExpression(string) {
|
||||
string = '' + string;
|
||||
}
|
||||
|
||||
if (!possible.test(string)) {
|
||||
return string;
|
||||
}
|
||||
if (!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
@@ -100,12 +93,6 @@ export function isEmpty(value) {
|
||||
}
|
||||
}
|
||||
|
||||
export function createFrame(object) {
|
||||
let frame = extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function blockParams(params, ids) {
|
||||
params.path = ids;
|
||||
return params;
|
||||
@@ -114,3 +101,5 @@ export function blockParams(params, ids) {
|
||||
export function appendContextPath(contextPath, id) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-var */
|
||||
|
||||
// var local = handlebars.create();
|
||||
|
||||
+100
-300
@@ -1,154 +1,9 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'neo-async';
|
||||
/*eslint-disable no-console */
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import { basename } from 'path';
|
||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||
|
||||
module.exports.loadTemplates = function(opts, callback) {
|
||||
loadStrings(opts, function(err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
loadFiles(opts, function(err, files) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
opts.templates = strings.concat(files);
|
||||
callback(undefined, opts);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function loadStrings(opts, callback) {
|
||||
let strings = arrayCast(opts.string),
|
||||
names = arrayCast(opts.name);
|
||||
|
||||
if (names.length !== strings.length && strings.length > 1) {
|
||||
return callback(
|
||||
new Handlebars.Exception(
|
||||
'Number of names did not match the number of string inputs'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Async.map(
|
||||
strings,
|
||||
function(string, callback) {
|
||||
if (string !== '-') {
|
||||
callback(undefined, string);
|
||||
} else {
|
||||
// Load from stdin
|
||||
let buffer = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', function(chunk) {
|
||||
buffer += chunk;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(undefined, buffer);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err, strings) {
|
||||
strings = strings.map((string, index) => ({
|
||||
name: names[index],
|
||||
path: names[index],
|
||||
source: string
|
||||
}));
|
||||
callback(err, strings);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loadFiles(opts, callback) {
|
||||
// Build file extension pattern
|
||||
let extension = (opts.extension || 'handlebars').replace(
|
||||
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||
function(arg) {
|
||||
return '\\' + arg;
|
||||
}
|
||||
);
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let ret = [],
|
||||
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
|
||||
Async.whilst(
|
||||
() => queue.length,
|
||||
function(callback) {
|
||||
let { template: path, root } = queue.shift();
|
||||
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err) {
|
||||
return callback(
|
||||
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||
);
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdir(path, function(err, children) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
children.forEach(function(file) {
|
||||
let childPath = path + '/' + file;
|
||||
|
||||
if (
|
||||
extension.test(childPath) ||
|
||||
fs.statSync(childPath).isDirectory()
|
||||
) {
|
||||
queue.push({ template: childPath, root: root || path });
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function(err, data) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
let name = path;
|
||||
if (!root) {
|
||||
name = basename(name);
|
||||
} else if (name.indexOf(root) === 0) {
|
||||
name = name.substring(root.length + 1);
|
||||
}
|
||||
name = name.replace(extension, '');
|
||||
|
||||
ret.push({
|
||||
path: path,
|
||||
name: name,
|
||||
source: data
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(undefined, ret);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
if (opts.version) {
|
||||
@@ -156,31 +11,23 @@ module.exports.cli = function(opts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length && !opts.hasDirectory) {
|
||||
throw new Handlebars.Exception(
|
||||
'Must define at least one template or directory.'
|
||||
);
|
||||
if (!opts.templates.length) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception(`Unable to open template file "${template}"`);
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||
}
|
||||
|
||||
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
||||
if (opts.simple && multiple) {
|
||||
throw new Handlebars.Exception(
|
||||
'Unable to output multiple templates in simple mode'
|
||||
);
|
||||
}
|
||||
|
||||
// Force simple mode if we have only one template and it's unnamed.
|
||||
if (
|
||||
!opts.amd &&
|
||||
!opts.commonjs &&
|
||||
opts.templates.length === 1 &&
|
||||
!opts.templates[0].name
|
||||
) {
|
||||
opts.simple = true;
|
||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
}
|
||||
|
||||
// Convert the known list into a hash
|
||||
@@ -194,26 +41,16 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||
|
||||
if (opts.namespace && !isValidNamespace(opts.namespace)) {
|
||||
throw new Handlebars.Exception('Invalid namespace format');
|
||||
}
|
||||
// Build file extension pattern
|
||||
let extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
const runtimeModulePath =
|
||||
(opts.handlebarPath || '') + 'handlebars.runtime';
|
||||
output.add(
|
||||
'define([' +
|
||||
quoteForJavaScript(runtimeModulePath) +
|
||||
'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||
);
|
||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (opts.commonjs) {
|
||||
output.add(
|
||||
'var Handlebars = require(' + quoteForJavaScript(opts.commonjs) + ');'
|
||||
);
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
@@ -226,57 +63,81 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
let path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
let childPath = template + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
processTemplate(childPath, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length + 1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
let precompiled = Handlebars.precompile(data, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = template.path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
let precompiled = Handlebars.precompile(template.source, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(
|
||||
precompiled.code,
|
||||
consumer
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else {
|
||||
if (!template.name) {
|
||||
throw new Handlebars.Exception('Name missing for template');
|
||||
}
|
||||
|
||||
if (opts.amd && !multiple) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add([
|
||||
objectName,
|
||||
'[',
|
||||
quoteForJavaScript(template.name),
|
||||
'] = template(',
|
||||
precompiled,
|
||||
');\n'
|
||||
]);
|
||||
}
|
||||
processTemplate(template, opts.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (multiple) {
|
||||
output.add(['return ', objectName, ';\n']);
|
||||
if (opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if (opts.partial) {
|
||||
output.add('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.add('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
@@ -284,17 +145,24 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (opts.map) {
|
||||
output.add(
|
||||
'\n//# sourceMappingURL=' + sanitizeSourceMapComment(opts.map) + '\n'
|
||||
);
|
||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
||||
}
|
||||
|
||||
output = output.toStringWithSourceMap();
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = minify(output, opts.map);
|
||||
output = uglify.minify(output.code, {
|
||||
fromString: true,
|
||||
|
||||
outSourceMap: opts.map,
|
||||
inSourceMap: JSON.parse(output.map)
|
||||
});
|
||||
if (opts.map) {
|
||||
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -308,71 +176,3 @@ module.exports.cli = function(opts) {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
|
||||
function arrayCast(value) {
|
||||
value = value != null ? value : [];
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Safely quotes a value for embedding in generated JavaScript strings
|
||||
*
|
||||
* Uses JSON.stringify which handles all special characters.
|
||||
*/
|
||||
function quoteForJavaScript(value) {
|
||||
return JSON.stringify(String(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a namespace is a legitimate dotted JavaScript identifier
|
||||
* (e.g. "App.templates") to prevent arbitrary code injection
|
||||
*/
|
||||
function isValidNamespace(namespace) {
|
||||
return /^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(
|
||||
namespace
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips line terminators from source map URLs to prevent injection of new
|
||||
* JavaScript lines via the sourceMappingURL comment
|
||||
*/
|
||||
function sanitizeSourceMapComment(value) {
|
||||
return String(value).replace(/[\r\n\u2028\u2029]/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||
*
|
||||
* We are using `require` instead of `import` here, because es6-modules do not allow
|
||||
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
|
||||
* should not be a problem.
|
||||
*
|
||||
* @param {string} output the compiled template
|
||||
* @param {string} sourceMapFile the file to write the source map to.
|
||||
*/
|
||||
function minify(output, sourceMapFile) {
|
||||
try {
|
||||
// Try to resolve uglify-js in order to see if it does exist
|
||||
require.resolve('uglify-js');
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
// Something else seems to be wrong
|
||||
throw e;
|
||||
}
|
||||
// it does not exist!
|
||||
console.error(
|
||||
'Code minimization is disabled due to missing uglify-js dependency'
|
||||
);
|
||||
return output;
|
||||
}
|
||||
return require('uglify-js').minify(output.code, {
|
||||
sourceMap: {
|
||||
content: output.map,
|
||||
url: sourceMapFile
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
'check-coverage': true,
|
||||
branches: 100,
|
||||
lines: 100,
|
||||
functions: 100,
|
||||
statements: 100,
|
||||
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
|
||||
reporter: 'html'
|
||||
};
|
||||
Generated
+3547
-16146
File diff suppressed because it is too large
Load Diff
+29
-83
@@ -1,18 +1,21 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "4.7.9",
|
||||
"version": "3.0.8",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "https://handlebarsjs.com/",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"template",
|
||||
"html"
|
||||
],
|
||||
"publishConfig": {
|
||||
"tag": "legacy"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
@@ -21,80 +24,49 @@
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.1.40"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
"uglify-js": "^2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.44.1",
|
||||
"async": "^0.9.0",
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-diff": "^1.0.1",
|
||||
"concurrently": "^5.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"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.5.3",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-cli": "^1",
|
||||
"grunt-contrib-clean": "^1",
|
||||
"grunt-contrib-concat": "^1",
|
||||
"grunt-contrib-connect": "^1",
|
||||
"grunt-contrib-copy": "^1",
|
||||
"grunt-contrib-requirejs": "^1",
|
||||
"grunt-contrib-uglify": "^1",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-shell": "^4.0.0",
|
||||
"grunt-cli": "~0.1.10",
|
||||
"grunt-contrib-clean": "0.x",
|
||||
"grunt-contrib-concat": "0.x",
|
||||
"grunt-contrib-connect": "0.x",
|
||||
"grunt-contrib-copy": "0.x",
|
||||
"grunt-contrib-requirejs": "0.x",
|
||||
"grunt-contrib-uglify": "0.x",
|
||||
"grunt-contrib-watch": "0.x",
|
||||
"grunt-eslint": "^11.0.0",
|
||||
"grunt-saucelabs": "9.x",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"husky": "^3.1.0",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mocha": "^5",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"nyc": "^14.1.1",
|
||||
"prettier": "^1.19.1",
|
||||
"semver": "^5.0.1",
|
||||
"sinon": "^7.5.0",
|
||||
"typescript": "^3.4.3",
|
||||
"keen.io": "0.0.3",
|
||||
"mocha": "~1.21.5",
|
||||
"mustache": "0.x",
|
||||
"semver": "^4.0.0",
|
||||
"underscore": "^1.5.1",
|
||||
"webpack": "^1.12.6",
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
"webpack": "^1.15.0",
|
||||
"webpack-dev-server": "^1.16.5"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"browser": "./dist/cjs/handlebars.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"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": "tsc --noEmit --project 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"
|
||||
"test": "grunt"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
@@ -104,31 +76,5 @@
|
||||
"buildConfig": {
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true
|
||||
};
|
||||
+166
-796
File diff suppressed because it is too large
Load Diff
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
import Handlebars = require('handlebars')
|
||||
|
||||
declare module "handlebars/runtime" {
|
||||
|
||||
}
|
||||
+6
-17
@@ -1,21 +1,14 @@
|
||||
{
|
||||
"extends": [
|
||||
"../.eslintrc.js",
|
||||
"plugin:es5/no-es2015",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"es5"
|
||||
],
|
||||
"globals": {
|
||||
"CompilerContext": true,
|
||||
"Handlebars": true,
|
||||
"handlebarsEnv": true,
|
||||
|
||||
"shouldCompileTo": true,
|
||||
"shouldCompileToWithPartials": true,
|
||||
"shouldThrow": true,
|
||||
"expectTemplate": true,
|
||||
"compileWithPartials": true,
|
||||
|
||||
"console": true,
|
||||
"require": true,
|
||||
"suite": true,
|
||||
@@ -28,20 +21,16 @@
|
||||
"start": true,
|
||||
"stop": true,
|
||||
"ok": true,
|
||||
"sinon": true,
|
||||
"strictEqual": true,
|
||||
"define": true,
|
||||
"expect": true,
|
||||
"chai": true
|
||||
"define": true
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": "off",
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": "off",
|
||||
"dot-notation": "off"
|
||||
"no-var": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,13 @@
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/json2.js"></script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
@@ -59,7 +56,7 @@
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
// Reporting to test-runner
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
|
||||
+4
-8
@@ -1,4 +1,3 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
@@ -19,16 +18,13 @@
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/json2.js"></script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
@@ -79,7 +75,7 @@
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
// Reporting to test-runner
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{{#someHelper true}}
|
||||
<div>Some known helper</div>
|
||||
{{#anotherHelper true}}
|
||||
<div>Another known helper</div>
|
||||
{{/anotherHelper}}
|
||||
{{/someHelper}}
|
||||
@@ -1 +0,0 @@
|
||||
<div>This is a test</div>
|
||||
@@ -1 +0,0 @@
|
||||
<div>Test Partial</div>
|
||||
+130
-272
@@ -3,107 +3,113 @@ describe('ast', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var AST = Handlebars.AST;
|
||||
var LOCATION_INFO = {
|
||||
start: {
|
||||
line: 1,
|
||||
column: 1
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 1
|
||||
}
|
||||
};
|
||||
|
||||
function testLocationInfoStorage(node) {
|
||||
equals(node.loc.start.line, 1);
|
||||
equals(node.loc.start.column, 1);
|
||||
equals(node.loc.end.line, 1);
|
||||
equals(node.loc.end.column, 1);
|
||||
}
|
||||
|
||||
describe('MustacheStatement', function() {
|
||||
it('should store args', function() {
|
||||
var mustache = new handlebarsEnv.AST.MustacheStatement({}, null, null, true, {}, LOCATION_INFO);
|
||||
equals(mustache.type, 'MustacheStatement');
|
||||
equals(mustache.escaped, true);
|
||||
testLocationInfoStorage(mustache);
|
||||
});
|
||||
});
|
||||
describe('BlockStatement', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(
|
||||
function() {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
},
|
||||
Handlebars.Exception,
|
||||
"foo doesn't match bar - 2:5"
|
||||
);
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
|
||||
});
|
||||
|
||||
it('stores location info', function() {
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheStatement([{ original: 'foo'}], null, null, false, {});
|
||||
var block = new handlebarsEnv.AST.BlockStatement(
|
||||
mustacheNode,
|
||||
null, null,
|
||||
{body: []},
|
||||
{body: []},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
LOCATION_INFO);
|
||||
testLocationInfoStorage(block);
|
||||
});
|
||||
});
|
||||
describe('PathExpression', function() {
|
||||
it('stores location info', function() {
|
||||
var idNode = new handlebarsEnv.AST.PathExpression(false, 0, [], 'foo', LOCATION_INFO);
|
||||
testLocationInfoStorage(idNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe('helpers', function() {
|
||||
describe('#helperExpression', function() {
|
||||
it('should handle mustache statements', function() {
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: undefined
|
||||
}),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [1],
|
||||
hash: undefined
|
||||
}),
|
||||
true
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: {}
|
||||
}),
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should handle block statements', function() {
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: undefined
|
||||
}),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [1],
|
||||
hash: undefined
|
||||
}),
|
||||
true
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: {}
|
||||
}),
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should handle subexpressions', function() {
|
||||
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
|
||||
});
|
||||
it('should work with non-helper nodes', function() {
|
||||
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
|
||||
describe('Hash', function() {
|
||||
it('stores location info', function() {
|
||||
var hash = new handlebarsEnv.AST.Hash([], LOCATION_INFO);
|
||||
testLocationInfoStorage(hash);
|
||||
});
|
||||
});
|
||||
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'PartialStatement' }),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'ContentStatement' }),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'CommentStatement' }),
|
||||
false
|
||||
);
|
||||
describe('ContentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var content = new handlebarsEnv.AST.ContentStatement('HI', LOCATION_INFO);
|
||||
testLocationInfoStorage(content);
|
||||
});
|
||||
});
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
|
||||
describe('CommentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var comment = new handlebarsEnv.AST.CommentStatement('HI', {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(comment);
|
||||
});
|
||||
});
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
|
||||
false
|
||||
);
|
||||
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
|
||||
describe('NumberLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var integer = new handlebarsEnv.AST.NumberLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(integer);
|
||||
});
|
||||
});
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
|
||||
});
|
||||
describe('StringLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var string = new handlebarsEnv.AST.StringLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(string);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BooleanLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var bool = new handlebarsEnv.AST.BooleanLiteral('true', LOCATION_INFO);
|
||||
testLocationInfoStorage(bool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PartialStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var pn = new handlebarsEnv.AST.PartialStatement('so_partial', [], {}, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Program', function() {
|
||||
it('storing location info', function() {
|
||||
var pn = new handlebarsEnv.AST.Program([], null, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,21 +123,8 @@ describe('ast', function() {
|
||||
equals(node.loc.end.column, lastColumn);
|
||||
}
|
||||
|
||||
/* eslint-disable no-multi-spaces */
|
||||
ast = Handlebars.parse(
|
||||
'line 1 {{line1Token}}\n' + // 1
|
||||
' line 2 {{line2token}}\n' + // 2
|
||||
' line 3 {{#blockHelperOnLine3}}\n' + // 3
|
||||
'line 4{{line4token}}\n' + // 4
|
||||
'line5{{else}}\n' + // 5
|
||||
'{{line6Token}}\n' + // 6
|
||||
'{{/blockHelperOnLine3}}\n' + // 7
|
||||
'{{#open}}\n' + // 8
|
||||
'{{else inverse}}\n' + // 9
|
||||
'{{else}}\n' + // 10
|
||||
'{{/open}}'
|
||||
); // 11
|
||||
/* eslint-enable no-multi-spaces */
|
||||
ast = Handlebars.parse('line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n' +
|
||||
'line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}');
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function() {
|
||||
@@ -151,71 +144,26 @@ describe('ast', function() {
|
||||
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||
var secondMustacheStatement = body[3];
|
||||
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||
});
|
||||
});
|
||||
|
||||
it('gets the block helper information correct', function() {
|
||||
var blockHelperNode = body[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
it('gets the block helper information correct', function() {
|
||||
var blockHelperNode = body[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers the program of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
it('correctly records the line numbers the program of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 31, 5);
|
||||
});
|
||||
testColumns(program, 3, 5, 8, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 7, 13, 0);
|
||||
});
|
||||
|
||||
it('correctly records the line number of chained inverses', function() {
|
||||
var chainInverseNode = body[7];
|
||||
|
||||
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
||||
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
|
||||
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
|
||||
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('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 ');
|
||||
});
|
||||
});
|
||||
testColumns(inverse, 5, 7, 5, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone flags', function() {
|
||||
@@ -226,70 +174,10 @@ describe('ast', function() {
|
||||
equals(!!ast.body[2].value, true);
|
||||
});
|
||||
});
|
||||
describe('blocks - parseWithoutProcessing', function() {
|
||||
it('block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, ' ');
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
it('initial block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{# comment}} \nfoo\n {{/comment}}'
|
||||
),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
});
|
||||
it('mustaches with children', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{# comment}} \n{{foo}}\n {{/comment}}'
|
||||
),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \n');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n ');
|
||||
});
|
||||
it('nested block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, ' \n');
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
});
|
||||
it('column 0 block mustaches', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(
|
||||
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
|
||||
@@ -300,24 +188,22 @@ describe('ast', function() {
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, '');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n');
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, '');
|
||||
|
||||
@@ -327,11 +213,9 @@ describe('ast', function() {
|
||||
equals(body[0].value, '');
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
|
||||
@@ -341,11 +225,9 @@ describe('ast', function() {
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
|
||||
),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[0];
|
||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
@@ -354,10 +236,8 @@ describe('ast', function() {
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(
|
||||
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||
),
|
||||
block = ast.body[1];
|
||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
@@ -367,18 +247,6 @@ describe('ast', function() {
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
});
|
||||
describe('partials - parseWithoutProcessing', function() {
|
||||
it('simple partial', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
|
||||
equals(ast.body[1].value, ' ');
|
||||
});
|
||||
it('indented partial', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[1].indent, '');
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
@@ -398,17 +266,6 @@ describe('ast', function() {
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments - parseWithoutProcessing', function() {
|
||||
it('simple comment', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
|
||||
equals(ast.body[1].value, ' ');
|
||||
});
|
||||
it('indented comment', function() {
|
||||
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
|
||||
equals(ast.body[0].value, ' ');
|
||||
equals(ast.body[2].value, ' ');
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
@@ -429,3 +286,4 @@ describe('ast', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+171
-470
@@ -6,591 +6,292 @@ beforeEach(function() {
|
||||
|
||||
describe('basic context', function() {
|
||||
it('most basic', function() {
|
||||
expectTemplate('{{foo}}')
|
||||
.withInput({ foo: 'foo' })
|
||||
.toCompileTo('foo');
|
||||
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it('escaping', function() {
|
||||
expectTemplate('\\{{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('{{foo}}');
|
||||
|
||||
expectTemplate('content \\{{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('content {{foo}}');
|
||||
|
||||
expectTemplate('\\\\{{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('\\food');
|
||||
|
||||
expectTemplate('content \\\\{{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('content \\food');
|
||||
|
||||
expectTemplate('\\\\ {{foo}}')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('\\\\ food');
|
||||
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
||||
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
||||
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
||||
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
|
||||
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
||||
});
|
||||
|
||||
it('compiling with a basic context', function() {
|
||||
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
|
||||
.withInput({
|
||||
cruel: 'cruel',
|
||||
world: 'world'
|
||||
})
|
||||
.withMessage('It works if all the required keys are provided')
|
||||
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||
});
|
||||
|
||||
it('compiling with a string context', function() {
|
||||
expectTemplate('{{.}}{{length}}')
|
||||
.withInput('bye')
|
||||
.toCompileTo('bye3');
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'It works if all the required keys are provided');
|
||||
});
|
||||
|
||||
it('compiling with an undefined context', function() {
|
||||
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
|
||||
.withInput(undefined)
|
||||
.toCompileTo('Goodbye\n\n!');
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
||||
|
||||
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
|
||||
.withInput(undefined)
|
||||
.toCompileTo('Goodbye');
|
||||
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
|
||||
});
|
||||
|
||||
it('comments', function() {
|
||||
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
|
||||
.withInput({
|
||||
cruel: 'cruel',
|
||||
world: 'world'
|
||||
})
|
||||
.withMessage('comments are ignored')
|
||||
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
||||
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'comments are ignored');
|
||||
|
||||
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
|
||||
|
||||
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
|
||||
'blah'
|
||||
);
|
||||
|
||||
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
|
||||
|
||||
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
|
||||
' blah'
|
||||
);
|
||||
|
||||
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
|
||||
|
||||
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
|
||||
' blah'
|
||||
);
|
||||
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
|
||||
});
|
||||
|
||||
it('boolean', function() {
|
||||
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbye: true,
|
||||
world: 'world'
|
||||
})
|
||||
.withMessage('booleans show the contents when true')
|
||||
.toCompileTo('GOODBYE cruel world!');
|
||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'booleans show the contents when true');
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
goodbye: false,
|
||||
world: 'world'
|
||||
})
|
||||
.withMessage('booleans do not show the contents when false')
|
||||
.toCompileTo('cruel world!');
|
||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
||||
'booleans do not show the contents when false');
|
||||
});
|
||||
|
||||
it('zeros', function() {
|
||||
expectTemplate('num1: {{num1}}, num2: {{num2}}')
|
||||
.withInput({
|
||||
num1: 42,
|
||||
num2: 0
|
||||
})
|
||||
.toCompileTo('num1: 42, num2: 0');
|
||||
|
||||
expectTemplate('num: {{.}}')
|
||||
.withInput(0)
|
||||
.toCompileTo('num: 0');
|
||||
|
||||
expectTemplate('num: {{num1/num2}}')
|
||||
.withInput({ num1: { num2: 0 } })
|
||||
.toCompileTo('num: 0');
|
||||
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
|
||||
'num1: 42, num2: 0');
|
||||
shouldCompileTo('num: {{.}}', 0, 'num: 0');
|
||||
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
|
||||
});
|
||||
|
||||
it('false', function() {
|
||||
/* eslint-disable no-new-wrappers */
|
||||
expectTemplate('val1: {{val1}}, val2: {{val2}}')
|
||||
.withInput({
|
||||
val1: false,
|
||||
val2: new Boolean(false)
|
||||
})
|
||||
.toCompileTo('val1: false, val2: false');
|
||||
/*eslint-disable no-new-wrappers */
|
||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
||||
|
||||
expectTemplate('val: {{.}}')
|
||||
.withInput(false)
|
||||
.toCompileTo('val: false');
|
||||
|
||||
expectTemplate('val: {{val1/val2}}')
|
||||
.withInput({ val1: { val2: false } })
|
||||
.toCompileTo('val: false');
|
||||
|
||||
expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
|
||||
.withInput({
|
||||
val1: false,
|
||||
val2: new Boolean(false)
|
||||
})
|
||||
.toCompileTo('val1: false, val2: false');
|
||||
|
||||
expectTemplate('val: {{{val1/val2}}}')
|
||||
.withInput({ val1: { val2: false } })
|
||||
.toCompileTo('val: false');
|
||||
/* eslint-enable */
|
||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
||||
/*eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle undefined and null', function() {
|
||||
expectTemplate('{{awesome undefined null}}')
|
||||
.withInput({
|
||||
awesome: function(_undefined, _null, options) {
|
||||
return (
|
||||
(_undefined === undefined) +
|
||||
' ' +
|
||||
(_null === null) +
|
||||
' ' +
|
||||
typeof options
|
||||
);
|
||||
}
|
||||
})
|
||||
.toCompileTo('true true object');
|
||||
|
||||
expectTemplate('{{undefined}}')
|
||||
.withInput({
|
||||
undefined: function() {
|
||||
return 'undefined!';
|
||||
}
|
||||
})
|
||||
.toCompileTo('undefined!');
|
||||
|
||||
expectTemplate('{{null}}')
|
||||
.withInput({
|
||||
null: function() {
|
||||
return 'null!';
|
||||
}
|
||||
})
|
||||
.toCompileTo('null!');
|
||||
shouldCompileTo('{{awesome undefined null}}',
|
||||
{
|
||||
awesome: function(_undefined, _null, options) {
|
||||
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
|
||||
}
|
||||
},
|
||||
'true true object');
|
||||
shouldCompileTo('{{undefined}}',
|
||||
{
|
||||
'undefined': function() {
|
||||
return 'undefined!';
|
||||
}
|
||||
},
|
||||
'undefined!');
|
||||
shouldCompileTo('{{null}}',
|
||||
{
|
||||
'null': function() {
|
||||
return 'null!';
|
||||
}
|
||||
},
|
||||
'null!');
|
||||
});
|
||||
|
||||
it('newlines', function() {
|
||||
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
|
||||
|
||||
expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
|
||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
||||
});
|
||||
|
||||
it('escaping text', function() {
|
||||
expectTemplate("Awesome's")
|
||||
.withMessage(
|
||||
"text is escaped so that it doesn't get caught on single quotes"
|
||||
)
|
||||
.toCompileTo("Awesome's");
|
||||
|
||||
expectTemplate('Awesome\\')
|
||||
.withMessage("text is escaped so that the closing quote can't be ignored")
|
||||
.toCompileTo('Awesome\\');
|
||||
|
||||
expectTemplate('Awesome\\\\ foo')
|
||||
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||
.toCompileTo('Awesome\\\\ foo');
|
||||
|
||||
expectTemplate('Awesome {{foo}}')
|
||||
.withInput({ foo: '\\' })
|
||||
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||
.toCompileTo('Awesome \\');
|
||||
|
||||
expectTemplate(" ' ' ")
|
||||
.withMessage('double quotes never produce invalid javascript')
|
||||
.toCompileTo(" ' ' ");
|
||||
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
|
||||
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
|
||||
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
|
||||
});
|
||||
|
||||
it('escaping expressions', function() {
|
||||
expectTemplate('{{{awesome}}}')
|
||||
.withInput({ awesome: "&'\\<>" })
|
||||
.withMessage("expressions with 3 handlebars aren't escaped")
|
||||
.toCompileTo("&'\\<>");
|
||||
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with 3 handlebars aren't escaped");
|
||||
|
||||
expectTemplate('{{&awesome}}')
|
||||
.withInput({ awesome: "&'\\<>" })
|
||||
.withMessage("expressions with {{& handlebars aren't escaped")
|
||||
.toCompileTo("&'\\<>");
|
||||
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with {{& handlebars aren't escaped");
|
||||
|
||||
expectTemplate('{{awesome}}')
|
||||
.withInput({ awesome: '&"\'`\\<>' })
|
||||
.withMessage('by default expressions should be escaped')
|
||||
.toCompileTo('&"'`\\<>');
|
||||
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
'by default expressions should be escaped');
|
||||
|
||||
expectTemplate('{{awesome}}')
|
||||
.withInput({ awesome: 'Escaped, <b> looks like: <b>' })
|
||||
.withMessage('escaping should properly handle amperstands')
|
||||
.toCompileTo('Escaped, <b> looks like: &lt;b&gt;');
|
||||
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: <b>'}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
'escaping should properly handle amperstands');
|
||||
});
|
||||
|
||||
it("functions returning safestrings shouldn't be escaped", function() {
|
||||
expectTemplate('{{awesome}}')
|
||||
.withInput({
|
||||
awesome: function() {
|
||||
return new Handlebars.SafeString("&'\\<>");
|
||||
}
|
||||
})
|
||||
.withMessage("functions returning safestrings aren't escaped")
|
||||
.toCompileTo("&'\\<>");
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
|
||||
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
});
|
||||
|
||||
it('functions', function() {
|
||||
expectTemplate('{{awesome}}')
|
||||
.withInput({
|
||||
awesome: function() {
|
||||
return 'Awesome';
|
||||
}
|
||||
})
|
||||
.withMessage('functions are called and render their output')
|
||||
.toCompileTo('Awesome');
|
||||
|
||||
expectTemplate('{{awesome}}')
|
||||
.withInput({
|
||||
awesome: function() {
|
||||
return this.more;
|
||||
},
|
||||
more: 'More awesome'
|
||||
})
|
||||
.withMessage('functions are bound to the context')
|
||||
.toCompileTo('More awesome');
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
|
||||
'functions are called and render their output');
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
|
||||
'functions are bound to the context');
|
||||
});
|
||||
|
||||
it('functions with context argument', function() {
|
||||
expectTemplate('{{awesome frank}}')
|
||||
.withInput({
|
||||
awesome: function(context) {
|
||||
return context;
|
||||
},
|
||||
frank: 'Frank'
|
||||
})
|
||||
.withMessage('functions are called with context arguments')
|
||||
.toCompileTo('Frank');
|
||||
shouldCompileTo('{{awesome frank}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
|
||||
it('pathed functions with context argument', function() {
|
||||
expectTemplate('{{bar.awesome frank}}')
|
||||
.withInput({
|
||||
bar: {
|
||||
awesome: function(context) {
|
||||
return context;
|
||||
}
|
||||
},
|
||||
frank: 'Frank'
|
||||
})
|
||||
.withMessage('functions are called with context arguments')
|
||||
.toCompileTo('Frank');
|
||||
shouldCompileTo('{{bar.awesome frank}}',
|
||||
{bar: {awesome: function(context) { return context; }},
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
|
||||
it('depthed functions with context argument', function() {
|
||||
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
|
||||
.withInput({
|
||||
awesome: function(context) {
|
||||
return context;
|
||||
},
|
||||
frank: 'Frank'
|
||||
})
|
||||
.withMessage('functions are called with context arguments')
|
||||
.toCompileTo('Frank');
|
||||
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
|
||||
it('block functions with context argument', function() {
|
||||
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
|
||||
.withInput({
|
||||
awesome: function(context, options) {
|
||||
return options.fn(context);
|
||||
}
|
||||
})
|
||||
.withMessage('block functions are called with context and options')
|
||||
.toCompileTo('inner 1');
|
||||
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
|
||||
{awesome: function(context, options) { return options.fn(context); }},
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it('depthed block functions with context argument', function() {
|
||||
expectTemplate(
|
||||
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
|
||||
)
|
||||
.withInput({
|
||||
value: true,
|
||||
awesome: function(context, options) {
|
||||
return options.fn(context);
|
||||
}
|
||||
})
|
||||
.withMessage('block functions are called with context and options')
|
||||
.toCompileTo('inner 1');
|
||||
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function(context, options) { return options.fn(context); }},
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it('block functions without context argument', function() {
|
||||
expectTemplate('{{#awesome}}inner{{/awesome}}')
|
||||
.withInput({
|
||||
awesome: function(options) {
|
||||
return options.fn(this);
|
||||
}
|
||||
})
|
||||
.withMessage('block functions are called with options')
|
||||
.toCompileTo('inner');
|
||||
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
|
||||
{awesome: function(options) { return options.fn(this); }},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
|
||||
it('pathed block functions without context argument', function() {
|
||||
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
|
||||
.withInput({
|
||||
foo: {
|
||||
awesome: function() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withMessage('block functions are called with options')
|
||||
.toCompileTo('inner');
|
||||
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
|
||||
{foo: {awesome: function() { return this; }}},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
it('depthed block functions without context argument', function() {
|
||||
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function() { return this; }},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
|
||||
it('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() {
|
||||
expectTemplate('{{foo-bar}}')
|
||||
.withInput({ 'foo-bar': 'baz' })
|
||||
.withMessage('Paths can contain hyphens (-)')
|
||||
.toCompileTo('baz');
|
||||
|
||||
expectTemplate('{{foo.foo-bar}}')
|
||||
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||
.withMessage('Paths can contain hyphens (-)')
|
||||
.toCompileTo('baz');
|
||||
|
||||
expectTemplate('{{foo/foo-bar}}')
|
||||
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||
.withMessage('Paths can contain hyphens (-)')
|
||||
.toCompileTo('baz');
|
||||
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
});
|
||||
|
||||
it('nested paths', function() {
|
||||
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||
.withInput({ alan: { expression: 'beautiful' } })
|
||||
.withMessage('Nested paths access nested objects')
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Nested paths access nested objects');
|
||||
});
|
||||
|
||||
it('nested paths with empty string value', function() {
|
||||
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||
.withInput({ alan: { expression: '' } })
|
||||
.withMessage('Nested paths access nested objects with empty string')
|
||||
.toCompileTo('Goodbye world!');
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
|
||||
'Goodbye world!', 'Nested paths access nested objects with empty string');
|
||||
});
|
||||
|
||||
it('literal paths', function() {
|
||||
expectTemplate('Goodbye {{[@alan]/expression}} world!')
|
||||
.withInput({ '@alan': { expression: 'beautiful' } })
|
||||
.withMessage('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!');
|
||||
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
});
|
||||
|
||||
it('literal references', function() {
|
||||
expectTemplate('Goodbye {{[foo bar]}} world!')
|
||||
.withInput({ 'foo bar': 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
|
||||
expectTemplate('Goodbye {{"foo bar"}} world!')
|
||||
.withInput({ 'foo bar': 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
|
||||
expectTemplate("Goodbye {{'foo bar'}} world!")
|
||||
.withInput({ 'foo bar': 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
|
||||
expectTemplate('Goodbye {{"foo[bar"}} world!')
|
||||
.withInput({ 'foo[bar': 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
|
||||
expectTemplate('Goodbye {{"foo\'bar"}} world!')
|
||||
.withInput({ "foo'bar": 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
|
||||
expectTemplate("Goodbye {{'foo\"bar'}} world!")
|
||||
.withInput({ 'foo"bar': 'beautiful' })
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
});
|
||||
|
||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
expectTemplate('test: {{.}}')
|
||||
.withInput(null)
|
||||
.withHelpers({ helper: 'awesome' })
|
||||
.toCompileTo('test: ');
|
||||
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
|
||||
});
|
||||
|
||||
it('complex but empty paths', function() {
|
||||
expectTemplate('{{person/name}}')
|
||||
.withInput({ person: { name: null } })
|
||||
.toCompileTo('');
|
||||
|
||||
expectTemplate('{{person/name}}')
|
||||
.withInput({ person: {} })
|
||||
.toCompileTo('');
|
||||
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
|
||||
shouldCompileTo('{{person/name}}', {person: {}}, '');
|
||||
});
|
||||
|
||||
it('this keyword in paths', function() {
|
||||
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
|
||||
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||
.withMessage('This keyword in paths evaluates to current context')
|
||||
.toCompileTo('goodbyeGoodbyeGOODBYE');
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
|
||||
.withInput({
|
||||
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||
})
|
||||
.withMessage('This keyword evaluates in more complex paths')
|
||||
.toCompileTo('helloHelloHELLO');
|
||||
string = '{{#hellos}}{{this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it('this keyword nested inside path', function() {
|
||||
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
|
||||
Error,
|
||||
'Invalid path: text/this - 1:13'
|
||||
);
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
|
||||
}, Error, 'Invalid path: text/this - 1:13');
|
||||
|
||||
expectTemplate('{{[this]}}')
|
||||
.withInput({ this: 'bar' })
|
||||
.toCompileTo('bar');
|
||||
|
||||
expectTemplate('{{text/[this]}}')
|
||||
.withInput({ text: { this: 'bar' } })
|
||||
.toCompileTo('bar');
|
||||
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
|
||||
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
|
||||
});
|
||||
|
||||
it('this keyword in helpers', function() {
|
||||
var helpers = {
|
||||
foo: function(value) {
|
||||
var helpers = {foo: function(value) {
|
||||
return 'bar ' + value;
|
||||
}
|
||||
};
|
||||
}};
|
||||
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
|
||||
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||
.withHelpers(helpers)
|
||||
.withMessage('This keyword in paths evaluates to current context')
|
||||
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
|
||||
|
||||
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
|
||||
.withInput({
|
||||
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||
})
|
||||
.withHelpers(helpers)
|
||||
.withMessage('This keyword evaluates in more complex paths')
|
||||
.toCompileTo('bar hellobar Hellobar HELLO');
|
||||
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it('this keyword nested inside helpers param', function() {
|
||||
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
|
||||
Error,
|
||||
'Invalid path: text/this - 1:17'
|
||||
);
|
||||
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error, 'Invalid path: text/this - 1:17');
|
||||
|
||||
expectTemplate('{{foo [this]}}')
|
||||
.withInput({
|
||||
foo: function(value) {
|
||||
return value;
|
||||
},
|
||||
this: 'bar'
|
||||
})
|
||||
.toCompileTo('bar');
|
||||
|
||||
expectTemplate('{{foo text/[this]}}')
|
||||
.withInput({
|
||||
foo: function(value) {
|
||||
return value;
|
||||
},
|
||||
text: { this: 'bar' }
|
||||
})
|
||||
.toCompileTo('bar');
|
||||
shouldCompileTo(
|
||||
'{{foo [this]}}',
|
||||
{foo: function(value) { return value; }, 'this': 'bar'},
|
||||
'bar');
|
||||
shouldCompileTo(
|
||||
'{{foo text/[this]}}',
|
||||
{foo: function(value) { return value; }, text: {'this': 'bar'}},
|
||||
'bar');
|
||||
});
|
||||
|
||||
it('pass string literals', function() {
|
||||
expectTemplate('{{"foo"}}').toCompileTo('');
|
||||
|
||||
expectTemplate('{{"foo"}}')
|
||||
.withInput({ foo: 'bar' })
|
||||
.toCompileTo('bar');
|
||||
|
||||
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
|
||||
.withInput({
|
||||
foo: ['bar', 'baz']
|
||||
})
|
||||
.toCompileTo('barbaz');
|
||||
shouldCompileTo('{{"foo"}}', {}, '');
|
||||
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
|
||||
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
|
||||
});
|
||||
|
||||
it('pass number literals', function() {
|
||||
expectTemplate('{{12}}').toCompileTo('');
|
||||
|
||||
expectTemplate('{{12}}')
|
||||
.withInput({ '12': 'bar' })
|
||||
.toCompileTo('bar');
|
||||
|
||||
expectTemplate('{{12.34}}').toCompileTo('');
|
||||
|
||||
expectTemplate('{{12.34}}')
|
||||
.withInput({ '12.34': 'bar' })
|
||||
.toCompileTo('bar');
|
||||
|
||||
expectTemplate('{{12.34 1}}')
|
||||
.withInput({
|
||||
'12.34': function(arg) {
|
||||
return 'bar' + arg;
|
||||
}
|
||||
})
|
||||
.toCompileTo('bar1');
|
||||
shouldCompileTo('{{12}}', {}, '');
|
||||
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34}}', {}, '');
|
||||
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
|
||||
});
|
||||
|
||||
it('pass boolean literals', function() {
|
||||
expectTemplate('{{true}}').toCompileTo('');
|
||||
|
||||
expectTemplate('{{true}}')
|
||||
.withInput({ '': 'foo' })
|
||||
.toCompileTo('');
|
||||
|
||||
expectTemplate('{{false}}')
|
||||
.withInput({ false: 'foo' })
|
||||
.toCompileTo('foo');
|
||||
shouldCompileTo('{{true}}', {}, '');
|
||||
shouldCompileTo('{{true}}', { '': 'foo' }, '');
|
||||
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it('should handle literals in subexpression', function() {
|
||||
expectTemplate('{{foo (false)}}')
|
||||
.withInput({
|
||||
false: function() {
|
||||
return 'bar';
|
||||
}
|
||||
})
|
||||
.withHelper('foo', function(arg) {
|
||||
var helpers = {
|
||||
foo: function(arg) {
|
||||
return arg;
|
||||
})
|
||||
.toCompileTo('bar');
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user