Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: handlebars-lang/handlebars.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 7ef86173abb446cf564ad6dc2646a4c361e2ab9f
Choose a base ref
...
head repository: handlebars-lang/handlebars.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7adc19ab40917389fc1372d19677f1d024ec42b1
Choose a head ref

Commits on Nov 13, 2019

  1. test: add fluent API for testing Handlebars

    use "expectTemplate(template)....toCompileTo(output)"
    nknapp committed Nov 13, 2019
    Copy the full SHA
    c2ac79c View commit details
  2. Copy the full SHA
    d541378 View commit details
  3. Update release notes

    nknapp committed Nov 13, 2019
    Copy the full SHA
    6914090 View commit details
  4. v4.5.2

    nknapp committed Nov 13, 2019
    Copy the full SHA
    8de121d View commit details

Commits on Nov 15, 2019

  1. fix: use !== 0 instead of != 0

    nknapp committed Nov 15, 2019
    Copy the full SHA
    c02b05f View commit details
  2. Copy the full SHA
    93e284e View commit details
  3. test: add sinon.js for spies, deprecate current assertions

    - the goal is to get overall cleaner test.
    - chai and sinon are standard libraries for testing
      assertions and spies. We should not use custom ones.
    nknapp committed Nov 15, 2019
    Copy the full SHA
    93516a0 View commit details
  4. Copy the full SHA
    0817dad View commit details

Commits on Nov 17, 2019

  1. 1
    Copy the full SHA
    886ba86 View commit details

Commits on Nov 18, 2019

  1. fix: add more properties required to be enumerable

    - __defineGetter__, __defineSetter__, __lookupGetter__, __proto__
    nknapp committed Nov 18, 2019
    Copy the full SHA
    1988878 View commit details
  2. 1
    Copy the full SHA
    f7f05d7 View commit details
  3. Update release notes

    nknapp committed Nov 18, 2019
    Copy the full SHA
    827c9d0 View commit details
  4. v4.5.3

    nknapp committed Nov 18, 2019
    Copy the full SHA
    c819c8b View commit details
  5. fix(runtime.js): partials compile not caching (#1600)

    Reintroduce "merge" function, no called "mergeIfNeeded", that only creates a new partials
    object if both "env.partials" and "options.partials" are set.
    
    closes #1598
    ole-martin authored and nknapp committed Nov 18, 2019
    Copy the full SHA
    23d58e7 View commit details

Commits on Dec 2, 2019

  1. Update readme.md with updated links (#1620)

    * Update readme.md with updated links
    
    Fix http->https where possible and update links doc links
    
    * Update README.markdown
    
    * Fix precompilation link
    ole-martin authored and nknapp committed Dec 2, 2019
    Copy the full SHA
    edcc84f View commit details
  2. remove yarn.lock

    nknapp committed Dec 2, 2019
    Copy the full SHA
    587e7a3 View commit details
  3. chore: configure prettier and eslint

    - add prettier to do formatting
    - add eslint-config-prettier to
      disable rules conflicting with prettier
    - remove eslint from grunt workflow
    - use lint-stage to lint and format
      on precommit
    - use eslint and prettier in travis directly
    - remove rules that are already part of
      the "recommended" ruleset
    
    That rational is that eslint and prettier should be run in
    Travis-CI, on commit and as IDE integration (highlighting
    errors directlry). They don't need to be run along with
    test-cases. Getting linting errors when running the tests
    because of missing semicolons is just annoying, but doesn't
    help the overall code-quality.
    nknapp committed Dec 2, 2019
    Copy the full SHA
    1f61f21 View commit details

Commits on Dec 3, 2019

  1. chore: restructure build commands

    - move dtslint (checkTypes) away from Gruntfile.js
      (as it disturbs debugging `grunt`)
      and call it directly as npm-script in travis-ci
    - re-group task definition in Gruntfile.js
    - use a multi-job travis build to
      run linting, format, dtslint and tests
      in parallel
    - run only "npm test" on appveyor
    - rename Grunt-tasks for be more descriptive
    - SAUCE_USERNAME is not a secret variable anymore
      it can be easily guessed anyway and the
      [secure] value messes up with a lot of the
      log output
    
    - linting on commit disable during transition
    nknapp committed Dec 3, 2019
    Copy the full SHA
    e913dc5 View commit details
  2. Copy the full SHA
    e97685e View commit details
  3. chore: fix task name in build

    nknapp committed Dec 3, 2019
    Copy the full SHA
    8901c28 View commit details

Commits on Dec 5, 2019

  1. Copy the full SHA
    c40d9f3 View commit details

Commits on Dec 8, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    04b1984 View commit details
  2. Update (C) year in the LICENSE file

    Welcome to 2019!
    marado authored and nknapp committed Dec 8, 2019
    Copy the full SHA
    d1fb07b View commit details

Commits on Dec 14, 2019

  1. chore: change eslint-rules for tasks/

    - use es2017 rules as NodeJS supports it today
    - add "prefer-const"
    nknapp committed Dec 14, 2019
    Copy the full SHA
    dc54952 View commit details
  2. Copy the full SHA
    dde108e View commit details
  3. 1
    Copy the full SHA
    3a5b65e View commit details
  4. 1
    Copy the full SHA
    1ebce2b View commit details
  5. Copy the full SHA
    1ec1737 View commit details
  6. Copy the full SHA
    14b621c View commit details
  7. chore: disable "dot-notation" rule

    - I don't think it makes much sense. In some cases,
      it is more readable to wrap property access in quotes,
      some times not, but there is no universal rule
      for that.
    - a promises "catch"-function should not be wrapped, but it has to
      be, if "allow-keywords" is set to false
    - an "if"-helper should be wrapped, but it is not allowed to be
      if "allow-keywords" is set to true (default).
    nknapp committed Dec 14, 2019
    Copy the full SHA
    ac4655e View commit details
  8. chore: ignore .nyc_output

    nknapp committed Dec 14, 2019
    Copy the full SHA
    164b7ff View commit details

Commits on Jan 8, 2020

  1. feat: access control to prototype properties via whitelist

    Disallow access to prototype properties and methods by default.
    Access to properties is always checked via
    `Object.prototype.hasOwnProperty.call(parent, propertyName)`.
    
    New runtime options:
    - **allowedProtoMethods**: a string-to-boolean map of property-names that are allowed if they are methods of the parent object.
    - **allowedProtoProperties**: a string-to-boolean map of property-names that are allowed if they are properties but not methods of the parent object.
    
    ```js
    const template = handlebars.compile('{{aString.trim}}')
    const result = template({ aString: '  abc  ' })
    // result is empty, because trim is defined at String prototype
    ```
    
    ```js
    const template = handlebars.compile('{{aString.trim}}')
    const result = template({ aString: '  abc  ' }, {
      allowedProtoMethods: {
        trim: true
      }
    })
    // result = 'abc'
    ```
    
    Implementation details: The method now "container.lookupProperty"
    handles the prototype-checks and the white-lists. It is used in
    - JavaScriptCompiler#nameLookup
    - The "lookup"-helper (passed to all helpers as "options.lookupProperty")
    - The "lookup" function at the container, which is used for recursive lookups in "compat" mode
    
    Compatibility:
    - **Old precompiled templates work with new runtimes**: The "options.lookupPropery"-function is passed to the helper by a wrapper, not by the compiled templated.
    - **New templates work with old runtimes**: The template contains a function that is used as fallback if the "lookupProperty"-function cannot be found at the container. However, the runtime-options "allowedProtoProperties" and "allowedProtoMethods" only work with the newest runtime.
    
    BREAKING CHANGE:
    - access to prototype properties is forbidden completely by default
    nknapp committed Jan 8, 2020
    Copy the full SHA
    d03b6ec View commit details
  2. Copy the full SHA
    d337f40 View commit details
  3. test: add path to nodeJs when running test:bin

    - this allows the test to be run in a debugger
      without the complete PATH
    nknapp committed Jan 8, 2020
    Copy the full SHA
    187d611 View commit details
  4. Copy the full SHA
    d7f0dcf View commit details
  5. Update release notes

    nknapp committed Jan 8, 2020
    Copy the full SHA
    770d746 View commit details
  6. v4.6.0

    nknapp committed Jan 8, 2020
    1
    Copy the full SHA
    91a1b5d View commit details

Commits on Jan 10, 2020

  1. feat: default options for controlling proto access

    This commmit adds the runtime options
    - `allowProtoPropertiesByDefault` (boolean, default: false) and
    - `allowProtoMethodsByDefault` (boolean, default: false)`
    which can be used to allow access to prototype properties and
    functions in general.
    
    Specific properties and methods can still be disabled from access
    via `allowedProtoProperties` and `allowedProtoMethods` by
    setting the corresponding values to false.
    
    The methods `constructor`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__`
    and the property `__proto__` will be disabled, even if the allow...ByDefault-options
    are set to true. In order to allow access to those properties and methods, they have
    to be explicitly set to true in the 'allowedProto...'-options.
    
    A warning is logged when the a proto-access it attempted and denied
    by default (i.e. if no option is set by the user to make the access
    decision explicit)
    nknapp committed Jan 10, 2020
    Copy the full SHA
    7af1c12 View commit details
  2. fix: use "logger" instead of console.error

    ... to be graceful with older browser without "console"
    nknapp committed Jan 10, 2020
    Copy the full SHA
    575d877 View commit details
  3. Update release notes

    nknapp committed Jan 10, 2020
    Copy the full SHA
    1f0834b View commit details
  4. v4.7.0

    nknapp committed Jan 10, 2020
    Copy the full SHA
    0d5c807 View commit details

Commits on Jan 12, 2020

  1. Copy the full SHA
    3c1e252 View commit details
  2. fix: fix log output in case of illegal property access

    - fix link url to handlebarsjs.com
    nknapp committed Jan 12, 2020
    Copy the full SHA
    f152dfc View commit details
  3. Update release notes

    nknapp committed Jan 12, 2020
    Copy the full SHA
    4cddfe7 View commit details
  4. v4.7.1

    nknapp committed Jan 12, 2020
    Copy the full SHA
    14ba3d0 View commit details

Commits on Jan 13, 2020

  1. fix: don't wrap helpers that are not functions

    - helpers should always be a function, but in #1639 one seems to
      be undefined. This was not a problem before 4.6 because helpers
      weren't wrapped then.
      Now, we must take care only to wrap helpers (when adding
      the "lookupProperty" function to the options), if they
      are really functions.
    nknapp committed Jan 13, 2020
    Copy the full SHA
    9d5aa36 View commit details
  2. chore: execute saucelabs-task only if access-key exists

    - up to now, the existance of the SAUCE_USERNAME was checked
      but this variable is even present in pull-requests from other
      repos. This means that builds fail, because the access key
      is not there.
      This change looks for SAUCE_ACCESS_KEY instead, which is
      a secure variable, only present in build originating from
      the handlebars.js repo.
    nknapp committed Jan 13, 2020
    Copy the full SHA
    a4fd391 View commit details
  3. Update release notes

    nknapp committed Jan 13, 2020
    Copy the full SHA
    f0c6c4c View commit details
  4. v4.7.2

    nknapp committed Jan 13, 2020
    Copy the full SHA
    586e672 View commit details

Commits on Jan 21, 2020

  1. Copy the full SHA
    ad63f51 View commit details
Showing with 12,851 additions and 10,836 deletions.
  1. +25 −0 .eslintignore
  2. +51 −102 .eslintrc.js
  3. +11 −6 .gitignore
  4. +0 −2 .istanbul.yml
  5. +22 −0 .prettierignore
  6. +33 −20 .travis.yml
  7. +31 −9 CONTRIBUTING.md
  8. +122 −79 Gruntfile.js
  9. +1 −1 LICENSE
  10. +9 −9 README.markdown
  11. +3 −4 appveyor.yml
  12. +2 −1 bench/.eslintrc
  13. +11 −6 bench/dist-size.js
  14. +1 −1 bench/index.js
  15. +9 −4 bench/precompile-size.js
  16. +2 −1 bench/templates/arguments.js
  17. +8 −1 bench/templates/array-each.js
  18. +8 −1 bench/templates/array-mustache.js
  19. +3 −3 bench/templates/complex.js
  20. +8 −1 bench/templates/data.js
  21. +9 −1 bench/templates/depth-1.js
  22. +13 −3 bench/templates/depth-2.js
  23. +1 −1 bench/templates/index.js
  24. +4 −1 bench/templates/partial-recursion.js
  25. +10 −2 bench/templates/partial.js
  26. +5 −3 bench/templates/paths.js
  27. +1 −2 bench/templates/variables.js
  28. +50 −26 bench/throughput.js
  29. +52 −26 bench/util/benchwarmer.js
  30. +3 −3 bench/util/template-runner.js
  31. +108 −112 bin/handlebars
  32. +1 −1 components/bower.json
  33. +1 −1 components/handlebars.js.nuspec
  34. +1 −1 components/package.json
  35. +9 −8 integration-testing/multi-nodejs-test/.eslintrc.js
  36. +1 −1 integration-testing/multi-nodejs-test/run-handlebars.js
  37. +3 −2 integration-testing/multi-nodejs-test/test.sh
  38. +5 −5 integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js
  39. +3 −3 integration-testing/webpack-babel-test/src/lib/assert.js
  40. +24 −24 integration-testing/webpack-babel-test/webpack.config.js
  41. +2 −2 integration-testing/webpack-test/src/handlebars-default-import-pre-4.2-test.js
  42. +2 −3 integration-testing/webpack-test/src/handlebars-default-import-test.js
  43. +6 −4 integration-testing/webpack-test/src/handlebars-loader-test.js
  44. +3 −3 integration-testing/webpack-test/src/handlebars-require-vs-import-test.js
  45. +2 −2 integration-testing/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js
  46. +2 −2 integration-testing/webpack-test/src/handlebars-wildcard-import-test.js
  47. +3 −3 integration-testing/webpack-test/src/lib/assert.js
  48. +12 −14 integration-testing/webpack-test/webpack.config.js
  49. +5 −1 lib/handlebars.js
  50. +22 −8 lib/handlebars/base.js
  51. +10 −6 lib/handlebars/compiler/ast.js
  52. +3 −1 lib/handlebars/compiler/base.js
  53. +26 −23 lib/handlebars/compiler/code-gen.js
  54. +97 −62 lib/handlebars/compiler/compiler.js
  55. +29 −20 lib/handlebars/compiler/helpers.js
  56. +231 −104 lib/handlebars/compiler/javascript-compiler.js
  57. +19 −12 lib/handlebars/compiler/printer.js
  58. +8 −1 lib/handlebars/compiler/visitor.js
  59. +47 −29 lib/handlebars/compiler/whitespace-control.js
  60. +0 −1 lib/handlebars/decorators.js
  61. +1 −1 lib/handlebars/decorators/inline.js
  62. +14 −6 lib/handlebars/exception.js
  63. +7 −4 lib/handlebars/helpers/block-helper-missing.js
  64. +35 −23 lib/handlebars/helpers/each.js
  65. +3 −1 lib/handlebars/helpers/helper-missing.js
  66. +14 −4 lib/handlebars/helpers/if.js
  67. +2 −2 lib/handlebars/helpers/log.js
  68. +3 −5 lib/handlebars/helpers/lookup.js
  69. +17 −4 lib/handlebars/helpers/with.js
  70. +11 −0 lib/handlebars/internal/create-new-lookup-object.js
  71. +70 −0 lib/handlebars/internal/proto-access.js
  72. +13 −0 lib/handlebars/internal/wrapHelper.js
  73. +7 −3 lib/handlebars/logger.js
  74. +1 −2 lib/handlebars/no-conflict.js
  75. +202 −45 lib/handlebars/runtime.js
  76. +17 −10 lib/handlebars/utils.js
  77. +117 −75 lib/precompiler.js
  78. +9 −0 nyc.config.js
  79. +4,526 −1,932 package-lock.json
  80. +43 −10 package.json
  81. +5 −0 prettier.config.js
  82. +273 −50 release-notes.md
  83. +11 −5 spec/.eslintrc
  84. +6 −3 spec/amd-runtime.html
  85. +6 −3 spec/amd.html
  86. +6 −0 spec/artifacts/known.helpers.handlebars
  87. +1 −0 spec/artifacts/non.default.extension.hbs
  88. +1 −0 spec/artifacts/partial.template.handlebars
  89. +162 −82 spec/ast.js
  90. +442 −139 spec/basic.js
  91. +260 −102 spec/blocks.js
  92. +389 −109 spec/builtins.js
  93. +112 −35 spec/compiler.js
  94. +141 −48 spec/data.js
  95. +13 −2 spec/env/browser.js
  96. +142 −13 spec/env/common.js
  97. +8 −0 spec/env/node.js
  98. +12 −8 spec/env/runner.js
  99. +18 −3 spec/env/runtime.js
  100. +6 −0 spec/expected/bom.amd.js
  101. +3 −0 spec/expected/compiled.string.txt
  102. +1 −0 spec/expected/empty.amd.min.js
  103. +6 −0 spec/expected/empty.amd.namespace.js
  104. +3 −0 spec/expected/empty.amd.simple.js
  105. +6 −0 spec/expected/empty.common.js
  106. +10 −0 spec/expected/empty.name.amd.js
  107. +6 −0 spec/expected/empty.root.amd.js
  108. +6 −0 spec/expected/handlebar.path.amd.js
  109. +25 −0 spec/expected/help.menu.txt
  110. +10 −0 spec/expected/namespace.amd.js
  111. +6 −0 spec/expected/non.default.extension.amd.js
  112. +24 −0 spec/expected/non.empty.amd.known.helper.js
  113. +6 −0 spec/expected/partial.template.js
  114. +2 −0 spec/expected/source.map.amd.js
  115. +745 −238 spec/helpers.js
  116. +5 −2 spec/index.html
  117. +35 −4 spec/javascript-compiler.js
  118. +260 −98 spec/parser.js
  119. +504 −162 spec/partials.js
  120. +186 −92 spec/precompiler.js
  121. +303 −110 spec/regressions.js
  122. +2 −2 spec/require.js
  123. +75 −43 spec/runtime.js
  124. +408 −84 spec/security.js
  125. +15 −9 spec/source-map.js
  126. +26 −14 spec/spec.js
  127. +133 −62 spec/strict.js
  128. +110 −40 spec/string-params.js
  129. +75 −21 spec/subexpressions.js
  130. +1 −0 spec/tmp/.gitkeep
  131. +395 −48 spec/tokenizer.js
  132. +253 −63 spec/track-ids.js
  133. +6 −3 spec/umd-runtime.html
  134. +7 −3 spec/umd.html
  135. +16 −5 spec/utils.js
  136. 0 spec/{env → vendor}/json2.js
  137. 0 spec/{env → vendor}/require.js
  138. +50 −28 spec/visitor.js
  139. +65 −16 spec/whitespace-control.js
  140. +0 −16 tasks/.eslintrc
  141. +14 −0 tasks/.eslintrc.js
  142. +20 −14 tasks/metrics.js
  143. +26 −22 tasks/parser.js
  144. +102 −0 tasks/publish-to-aws.js
  145. +0 −91 tasks/publish.js
  146. +9 −0 tasks/task-tests/.eslintrc.js
  147. +1 −0 tasks/task-tests/README.md
  148. +121 −0 tasks/task-tests/git.test.js
  149. 0 tasks/task-tests/mocha.opts
  150. +232 −0 tasks/test-bin.js
  151. +22 −0 tasks/test-mocha.js
  152. +0 −77 tasks/test.js
  153. +13 −0 tasks/util/async-grunt-task.js
  154. +51 −0 tasks/util/exec-file.js
  155. +61 −95 tasks/util/git.js
  156. +47 −27 tasks/version.js
  157. +6 −0 types/index.d.ts
  158. +16 −0 types/test.ts
  159. +2 −1 types/tsconfig.json
  160. +0 −5,839 yarn.lock
25 changes: 25 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.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/
/integration-testing/*/dist/

# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor

# JS-Snippets
src/*.js
153 changes: 51 additions & 102 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,66 @@
module.exports = {
"extends": ["eslint:recommended","plugin:compat/recommended"],
"globals": {
"self": false
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
globals: {
self: false
},
"env": {
"node": true,
"es6": true
env: {
node: true,
es6: true
},
"rules": {
// overrides eslint:recommended defaults
"no-sparse-arrays": "off",
"no-func-assign": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-unreachable": "warn",

// Possible Errors //
//-----------------//
"no-unsafe-negation": "error",
rules: {
'no-console': 'warn',

// temporarily disabled until the violating places are fixed.
'no-func-assign': 'off',
'no-sparse-arrays': 'off',

// Best Practices //
//----------------//
"curly": "error",
"default-case": "warn",
"dot-notation": ["error", { "allowKeywords": false }],
"guard-for-in": "warn",
"no-alert": "error",
"no-caller": "error",
"no-div-regex": "warn",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-floating-decimal": "error",
"no-implied-eval": "error",
"no-iterator": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-loop-func": "error",
"no-multi-spaces": "error",
"no-multi-str": "warn",
"no-global-assign": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-process-env": "error",
"no-proto": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",

'default-case': 'warn',
'guard-for-in': 'warn',
'no-alert': 'error',
'no-caller': 'error',
'no-div-regex': 'warn',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-floating-decimal': 'error',
'no-implied-eval': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-multi-str': 'warn',
'no-global-assign': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-octal-escape': 'error',
'no-process-env': 'error',
'no-proto': 'error',
'no-return-assign': 'error',
'no-script-url': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-unused-expressions': 'error',
'no-warning-comments': 'warn',
'no-with': 'error',
radix: 'error',

// Variables //
//-----------//
"no-catch-shadow": "error",
"no-label-var": "error",
"no-shadow-restricted-names": "error",
"no-undef-init": "error",
"no-use-before-define": ["error", "nofunc"],


// Stylistic Issues //
//------------------//
"comma-dangle": ["error", "never"],
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "error",
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"consistent-this": ["warn", "self"],
"eol-last": "error",
"func-style": ["error", "declaration"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true
}],
"new-cap": "error",
"new-parens": "error",
"no-array-constructor": "error",
"no-lonely-if": "error",
"no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn",
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-extra-parens": ["error", "functions"],
"quotes": ["error", "single", "avoid-escape"],
"semi": "error",
"semi-spacing": ["error", { "before": false, "after": true }],
"keyword-spacing": "error",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": ["error", "always", { "markers": [","] }],
"wrap-regex": "warn",
'no-label-var': 'error',
'no-undef-init': 'error',
'no-use-before-define': ['error', 'nofunc'],

// ECMAScript 6 //
//--------------//
"no-var": "warn"
'no-var': 'error'
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {}
parserOptions: {
sourceType: 'module',
ecmaVersion: 6,
ecmaFeatures: {}
}
}
};
17 changes: 11 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
vendor
.rvmrc
.DS_Store
lib/handlebars/compiler/parser.js
/dist/
/tmp/
/coverage/
node_modules
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
/yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output

# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/
/spec/tmp/*
2 changes: 0 additions & 2 deletions .istanbul.yml

This file was deleted.

22 changes: 22 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output

# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/

# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
53 changes: 33 additions & 20 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
language: node_js
before_install:
- npm install -g grunt-cli
script:
- grunt --stack travis
jobs:
include:
- stage: test
name: check javascript (eslint)
node_js: lts/*
script: npm run lint
- stage: test
name: check formatting (prettier)
node_js: lts/*
script: npm run check-format
- stage: test
name: check typescript definitions (dtslint)
node_js: lts/*
script: npm run dtslint
- stage: test
name: extensive tests and publish to aws
script: npm run extensive-tests-and-publish-to-aws
env:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
- SAUCE_USERNAME=handlebars
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
node_js: '10'
- stage: test
name: test with latest nodejs-lts
node_js: lts/*
script: npm run test
- stage: test
name: test with active nodejs
node_js: node
script: npm run test
cache: npm
email:
on_failure: change
on_success: never
env:
global:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
matrix:
include:
- node_js: '10'
env:
- PUBLISH=true
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
cache:
directories:
- node_modules

git:
depth: 100
40 changes: 31 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -12,15 +12,16 @@ Documentation issues on the handlebarsjs.com site should be reported on [handleb

## 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.

## Pull Requests

We also accept [pull requests][pull-request]!

Generally we like to see pull requests that

- Maintain the existing code style
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
@@ -31,8 +32,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`.

@@ -51,22 +52,43 @@ If you notice any problems, please report them to the GitHub issue tracker at
##Running Tests

To run tests locally, first install all dependencies.

```sh
npm install
```

Clone the mustache specs into the spec/mustache folder.

```sh
cd spec
rm -r mustache
git clone https://github.com/mustache/spec.git mustache
```

From the root directory, run the tests.

```sh
npm test
```

## Linting and Formatting

Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
We do linting and formatting in two phases:

- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
while warnings are allowed.
- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
are forbidden.

You can use the following scripts to make sure that the travis-job does not fail:

- **npm run lint** will run `eslint` and fail on warnings
- **npm run format** will run `prettier` on all files
- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).

## Ember testing

The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
@@ -83,7 +105,7 @@ npm test

## Releasing the latest version

*When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch.*
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._

Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.

@@ -103,10 +125,10 @@ gem push handlebars-source-*.gem
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version

* [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
* [The bower package](https://github.com/components/handlebars.js) (check the package.json)
* [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
* [RubyGems](https://rubygems.org/gems/handlebars-source)
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
- [RubyGems](https://rubygems.org/gems/handlebars-source)

When everything is OK, the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.

201 changes: 122 additions & 79 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,42 @@
/* eslint-disable no-process-env */
module.exports = function(grunt) {

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),

eslint: {
files: [
'*.js',
'bench/**/*.js',
'tasks/**/*.js',
'lib/**/!(*.min|parser).js',
'spec/**/!(*.amd|json2|require).js',
'integration-testing/multi-nodejs-test/*.js',
'integration-testing/webpack-test/*.js',
'integration-testing/webpack-test/src/*.js'
]
},

clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js', 'integration-testing/**/node_modules'],
clean: [
'tmp',
'dist',
'lib/handlebars/compiler/parser.js',
'integration-testing/**/node_modules'
],

copy: {
dist: {
options: {
processContent: function(content) {
return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
+ content;
return (
grunt.template.process(
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("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' }
]
}
},
@@ -54,24 +51,28 @@ module.exports = function(grunt) {
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: {
@@ -80,7 +81,12 @@ 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&auxiliaryCommentBefore=istanbul%20ignore%20next'
}
]
},
output: {
@@ -129,15 +135,17 @@ module.exports = function(grunt) {
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
},
dist: {
files: [{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
files: [
{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
}
}
}]
]
}
},

@@ -161,44 +169,51 @@ module.exports = function(grunt) {
all: {
options: {
build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
urls: [
'http://localhost:9999/spec/?headless=true',
'http://localhost:9999/spec/amd.html?headless=true'
],
detailedError: true,
concurrency: 4,
browsers: [
{browserName: 'chrome'},
{browserName: 'firefox', platform: 'Linux'},
{ browserName: '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: 11,
platform: 'Windows 8.1'
},
{
browserName: 'internet explorer',
version: 10,
platform: 'Windows 8'
}
]
}
},
sanity: {
options: {
build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
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'}
]
browsers: [{ browserName: 'chrome' }]
}
}
},

bgShell: {
checkTypes: {
cmd: 'npm run checkTypes',
bg: false,
fail: true
},
integrationTests: {
cmd: './integration-testing/run-integration-tests.sh',
bg: false,
fail: true
}

},

watch: {
@@ -208,26 +223,11 @@ module.exports = function(grunt) {
},

files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['build', 'amd', 'tests', 'test']
tasks: ['on-file-change']
}
}
});

// Build a new version of the library
this.registerTask('build', 'Builds a distributable version of the current project', [
'eslint',
'bgShell:checkTypes',
'parser',
'node',
'globals']);

this.registerTask('amd', ['babel:amd', 'requirejs']);
this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);
this.registerTask('tests', ['concat:tests']);

this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);

// Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
@@ -238,18 +238,61 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-bg-shell');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
grunt.loadNpmTasks('grunt-webpack');

grunt.task.loadTasks('tasks');

this.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'globals']
);

this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);

this.registerTask('release', 'Build final packages', [
'amd',
'uglify',
'test:min',
'copy:dist',
'copy:components',
'copy:cdnjs'
]);

this.registerTask('amd', ['babel:amd', 'requirejs']);

this.registerTask('test', ['test:bin', 'test:cov']);

grunt.registerTask('bench', ['metrics']);
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);

grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'bgShell:integrationTests', 'sauce', 'metrics', 'publish:latest'] : ['default']);
if (process.env.SAUCE_ACCESS_KEY) {
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
} else {
grunt.registerTask('sauce', []);
}

// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'bgShell:integrationTests',
'sauce',
'metrics',
'publish-to-aws'
]);
grunt.registerTask('on-file-change', [
'build',
'amd',
'concat:tests',
'test'
]);

// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('integration-tests', ['default', 'bgShell:integrationTests']);
grunt.registerTask('integration-tests', [
'default',
'bgShell:integrationTests'
]);
};
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (C) 2011-2017 by Yehuda Katz
Copyright (C) 2011-2019 by Yehuda Katz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
18 changes: 9 additions & 9 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -11,12 +11,12 @@ Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be.

Checkout the official Handlebars docs site at
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).

Installing
----------

See our [installation documentation](http://handlebarsjs.com/installation.html).
See our [installation documentation](https://handlebarsjs.com/installation/).

Usage
-----
@@ -46,23 +46,23 @@ var result = template(data);
// </ul>
```

Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).

Precompiling Templates
----------------------

Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](http://handlebarsjs.com/precompilation.html).
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).

Differences Between Handlebars.js and Mustache
----------------------------------------------
Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work.

- [Nested Paths](http://handlebarsjs.com/#paths)
- [Helpers](http://handlebarsjs.com/#helpers)
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
- [Literal Values](http://handlebarsjs.com/#literals)
- [Delimited Comments](http://handlebarsjs.com/#comments)
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)

Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.

7 changes: 3 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -13,8 +13,7 @@ install:
# Clone submodules (mustache spec)
- cmd: git submodule update --init --recursive
# Install modules
- cmd: npm install
- cmd: npm install -g grunt-cli
- cmd: npm ci


# Post-install test scripts
@@ -23,7 +22,7 @@ test_script:
- cmd: node --version
- cmd: npm --version
# Run tests
- cmd: grunt --stack travis
- cmd: npm run test

# Don't actually build
build: off
@@ -34,4 +33,4 @@ on_failure:


# Set build version format here instead of in the admin panel
version: "{build}"
version: "{build}"
3 changes: 2 additions & 1 deletion bench/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"extends": "prettier",
"globals": {
"require": true
},
@@ -11,4 +12,4 @@
"handle-callback-err": 0,
"no-console": 0
}
}
}
17 changes: 11 additions & 6 deletions bench/dist-size.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
var async = require('neo-async'),
fs = require('fs'),
zlib = require('zlib');
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);
@@ -32,7 +34,10 @@ 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]);
});
}
);
};
2 changes: 1 addition & 1 deletion bench/index.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ var fs = require('fs');

var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) {
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
return;
}

13 changes: 9 additions & 4 deletions bench/precompile-size.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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
@@ -8,12 +8,17 @@ module.exports = function(grunt, callback) {
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]);
};
3 changes: 2 additions & 1 deletion bench/templates/arguments.js
Original file line number Diff line number Diff line change
@@ -8,5 +8,6 @@ module.exports = {
bar: true
},

handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
handlebars:
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
};
9 changes: 8 additions & 1 deletion bench/templates/array-each.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}',
9 changes: 8 additions & 1 deletion bench/templates/array-mustache.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#names}}{{name}}{{/names}}'
};
6 changes: 3 additions & 3 deletions bench/templates/complex.js
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@ module.exports = {
},
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' }
]
},

9 changes: 8 additions & 1 deletion bench/templates/data.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
};
10 changes: 9 additions & 1 deletion bench/templates/depth-1.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
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 %>'
16 changes: 13 additions & 3 deletions bench/templates/depth-2.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
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}}',
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 %>'
eco:
'<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
};
2 changes: 1 addition & 1 deletion bench/templates/index.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ var fs = require('fs');

var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) {
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
return;
}
module.exports[RegExp.$1] = require('./' + RegExp.$1);
5 changes: 4 additions & 1 deletion bench/templates/partial-recursion.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module.exports = {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
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}}' }
12 changes: 10 additions & 2 deletions bench/templates/partial.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
module.exports = {
context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
context: {
peeps: [
{ name: 'Moe', count: 15 },
{ name: 'Larry', count: 5 },
{ name: 'Curly', count: 1 }
]
},
partials: {
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
handlebars: {
variables: 'Hello {{name}}! You have {{count}} new messages.'
}
},

handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
8 changes: 5 additions & 3 deletions bench/templates/paths.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module.exports = {
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
context: { person: { name: { bar: { baz: 'Larry' } }, age: 45 } },
handlebars:
'{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
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}}'
};
3 changes: 1 addition & 2 deletions bench/templates/variables.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
module.exports = {
context: {name: 'Mick', count: 30},
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.'
};

76 changes: 50 additions & 26 deletions bench/throughput.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),

eco, dust, Handlebars, Mustache;
runner = require('./util/template-runner'),
eco,
dust,
Handlebars,
Mustache;

try {
dust = require('dustjs-linkedin');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}

try {
Mustache = require('mustache');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}

try {
eco = require('eco');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}

function error() {
throw new Error('EWOT');
@@ -22,21 +30,28 @@ function error() {
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,
partialName
) {
Handlebars.registerPartial(
partialName,
Handlebars.compile(partial, { data: false })
);
});

handlebarsOut = handlebar(context, options);
@@ -58,7 +73,9 @@ 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() {});
@@ -84,7 +101,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {

if (Mustache) {
var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache;
mustachePartials = partials && partials.mustache;

if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
@@ -107,9 +124,16 @@ 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
);
}
}

78 changes: 52 additions & 26 deletions bench/util/benchwarmer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var _ = require('underscore'),
Benchmark = require('benchmark');
Benchmark = require('benchmark');

function BenchWarmer() {
this.benchmarks = [];
@@ -29,20 +29,25 @@ 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,
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;
}
});
@@ -75,20 +80,23 @@ BenchWarmer.prototype = {
});
print('\n');

var errors = false, prop, bench;
var errors = false,
prop,
bench;
for (prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
if (
Object.prototype.hasOwnProperty.call(self, prop) &&
self.errors[prop].error.message !== 'EWOT'
) {
errors = true;
break;
}
}

if (errors) {
print('\n\nErrors:\n');
for (prop in self.errors) {
if (self.errors.hasOwnProperty(prop)
&& self.errors[prop].error.message !== 'EWOT') {
Object.keys(self.errors).forEach(function(prop) {
if (self.errors[prop].error.message !== 'EWOT') {
bench = self.errors[prop];
print('\n' + bench.name + ':\n');
print(bench.error.message);
@@ -97,7 +105,7 @@ BenchWarmer.prototype = {
}
print('\n');
}
}
});
}

callback();
@@ -108,23 +116,39 @@ BenchWarmer.prototype = {
},

scaleTimes: function() {
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);
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
);
},

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++) {
var name = names[i];

if (benchSize < name.length) { benchSize = name.length; }
if (benchSize < name.length) {
benchSize = name.length;
}
}

this.nameSize = benchSize + 2;
@@ -147,7 +171,9 @@ BenchWarmer.prototype = {
},

startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(
bench
) {
return bench.name.split(': ')[1];
});

@@ -165,9 +191,9 @@ BenchWarmer.prototype = {

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);
6 changes: 3 additions & 3 deletions bench/util/template-runner.js
Original file line number Diff line number Diff line change
@@ -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);
}
220 changes: 108 additions & 112 deletions bin/handlebars
Original file line number Diff line number Diff line change
@@ -1,127 +1,123 @@
#!/usr/bin/env node

var optimist = require('optimist')
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
'f': {
'type': 'string',
'description': 'Output File',
'alias': 'output'
},
'map': {
'type': 'string',
'description': 'Source Map File'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
'c': {
'type': 'string',
'description': 'Exports CommonJS style, path to Handlebars module',
'alias': 'commonjs',
'default': null
},
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
},
'k': {
'type': 'string',
'description': 'Known helpers',
'alias': 'known'
},
'o': {
'type': 'boolean',
'description': 'Known helpers only',
'alias': 'knownOnly'
},
'm': {
'type': 'boolean',
'description': 'Minimize output',
'alias': 'min'
},
'n': {
'type': 'string',
'description': 'Template namespace',
'alias': 'namespace',
'default': 'Handlebars.templates'
},
's': {
'type': 'boolean',
'description': 'Output template function only.',
'alias': 'simple'
},
'N': {
'type': 'string',
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
'alias': 'name'
},
'i': {
'type': 'string',
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
'alias': 'string'
},
'r': {
'type': 'string',
'description': 'Template root. Base value that will be stripped from template names.',
'alias': 'root'
},
'p': {
'type': 'boolean',
'description': 'Compiling a partial template',
'alias': 'partial'
},
'd': {
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
},
'e': {
'type': 'string',
'description': 'Template extension.',
'alias': 'extension',
'default': 'handlebars'
},
'b': {
'type': 'boolean',
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
'alias': 'bom'
},
'v': {
'type': 'boolean',
'description': 'Prints the current compiler version',
'alias': 'version'
},
const yargs = require('yargs')
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...')
.option('f', {
type: 'string',
description: 'Output File',
alias: 'output'
})
.option('map', {
type: 'string',
description: 'Source Map File'
})
.option('a', {
type: 'boolean',
description: 'Exports amd style (require.js)',
alias: 'amd'
})
.option('c', {
type: 'string',
description: 'Exports CommonJS style, path to Handlebars module',
alias: 'commonjs',
default: null
})
.option('h', {
type: 'string',
description: 'Path to handlebar.js (only valid for amd-style)',
alias: 'handlebarPath',
default: ''
})
.option('k', {
type: 'string',
description: 'Known helpers',
alias: 'known'
})
.option('o', {
type: 'boolean',
description: 'Known helpers only',
alias: 'knownOnly'
})
.option('m', {
type: 'boolean',
description: 'Minimize output',
alias: 'min'
})
.option('n', {
type: 'string',
description: 'Template namespace',
alias: 'namespace',
default: 'Handlebars.templates'
})
.option('s', {
type: 'boolean',
description: 'Output template function only.',
alias: 'simple'
})
.option('N', {
type: 'string',
description:
'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
alias: 'name'
})
.option('i', {
type: 'string',
description:
'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
alias: 'string'
})
.option('r', {
type: 'string',
description:
'Template root. Base value that will be stripped from template names.',
alias: 'root'
})
.option('p', {
type: 'boolean',
description: 'Compiling a partial template',
alias: 'partial'
})
.option('d', {
type: 'boolean',
description: 'Include data when compiling',
alias: 'data'
})
.option('e', {
type: 'string',
description: 'Template extension.',
alias: 'extension',
default: 'handlebars'
})
.option('b', {
type: 'boolean',
description:
'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
alias: 'bom'
})
.option('v', {
type: 'boolean',
description: 'Prints the current compiler version',
alias: 'version'
})
.option('help', {
type: 'boolean',
description: 'Outputs this message'
})
.wrap(120);

'help': {
'type': 'boolean',
'description': 'Outputs this message'
}
})

.wrap(120)
.check(function(argv) {
if (argv.version) {
return;
}
});


var argv = optimist.argv;
const argv = yargs.argv;
argv.files = argv._;
delete argv._;

var Precompiler = require('../dist/cjs/precompiler');
const Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function(err, opts) {

if (err) {
throw err;
}

if (opts.help || (!opts.templates.length && !opts.version)) {
optimist.showHelp();
yargs.showHelp();
} else {
Precompiler.cli(opts);
}
2 changes: 1 addition & 1 deletion components/bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.5.1",
"version": "4.7.4",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
2 changes: 1 addition & 1 deletion components/handlebars.js.nuspec
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
<package>
<metadata>
<id>handlebars.js</id>
<version>4.5.1</version>
<version>4.7.4</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
2 changes: 1 addition & 1 deletion components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.5.1",
"version": "4.7.4",
"license": "MIT",
"jspm": {
"main": "handlebars",
17 changes: 9 additions & 8 deletions integration-testing/multi-nodejs-test/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module.exports = {
"extends": "eslint:recommended",
"globals": {
"self": false
extends: ['eslint:recommended', 'plugin:es5/no-es2015', 'prettier'],
globals: {
self: false
},
"env": {
"node": true
env: {
node: true
},
"rules": {
'no-console': 'off'
rules: {
'no-console': 'off',
'no-var': 'off'
}
}
};
2 changes: 1 addition & 1 deletion integration-testing/multi-nodejs-test/run-handlebars.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ var Handlebars = require('handlebars');
console.log('Testing built Handlebars with Node version ' + process.version);

var template = Handlebars.compile('Author: {{author}}');
var output = template({author: 'Yehuda'});
var output = template({ author: 'Yehuda' });
assert.strictEqual(output, 'Author: Yehuda');

console.log('Success');
5 changes: 3 additions & 2 deletions integration-testing/multi-nodejs-test/test.sh
Original file line number Diff line number Diff line change
@@ -12,7 +12,8 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
# It does (almost) not test for correctness, because that is already done in the mocha-tests.
# And it does not use any NodeJS based testing framwork to make this part independent of the Node version.

# A list of NodeJS versions is expected as cli-args
unset npm_config_prefix

echo "Handlebars should be able to run in various versions of NodeJS"
for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do
rm target node_modules package-lock.json -rf
@@ -23,4 +24,4 @@ for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do
nvm exec "$i" npm run test-precompile || exit 1

echo Success
done
done
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as Handlebars from 'handlebars/runtime'
import * as Handlebars from 'handlebars/runtime';
import hbs from 'handlebars-inline-precompile';
import {assertEquals} from "../../webpack-test/src/lib/assert";
import { assertEquals } from '../../webpack-test/src/lib/assert';

Handlebars.registerHelper('loud', function(text) {
return text.toUpperCase();
return text.toUpperCase();
});

const template = hbs`{{loud name}}`;
const output = template({name: 'yehuda'})
const output = template({ name: 'yehuda' });

assertEquals(output, 'YEHUDA')
assertEquals(output, 'YEHUDA');
6 changes: 3 additions & 3 deletions integration-testing/webpack-babel-test/src/lib/assert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
48 changes: 24 additions & 24 deletions integration-testing/webpack-babel-test/webpack.config.js
Original file line number Diff line number Diff line change
@@ -3,30 +3,30 @@ const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});

module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: false },
}
}
]
},
optimization: {
minimize: false
}
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: false }
}
}
]
},
optimization: {
minimize: false
}
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Handlebars from 'handlebars/dist/handlebars';

import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';

const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Handlebars from 'handlebars';
import {assertEquals} from './lib/assert';

import { assertEquals } from './lib/assert';

const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
10 changes: 6 additions & 4 deletions integration-testing/webpack-test/src/handlebars-loader-test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';

import testTemplate from './test-template.handlebars';
assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda');

assertEquals(testTemplate({ author: 'Yehuda' }).trim(), 'Author: Yehuda');

const testTemplateRequire = require('./test-template.handlebars');
assertEquals(testTemplateRequire({author: 'Yehuda'}).trim(), 'Author: Yehuda');
assertEquals(
testTemplateRequire({ author: 'Yehuda' }).trim(),
'Author: Yehuda'
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as HandlebarsViaImport from 'handlebars';
const HandlebarsViaRequire = require('handlebars');
import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';

HandlebarsViaImport.registerHelper('loud', function(text) {
return text.toUpperCase();
return text.toUpperCase();
});

const template = HandlebarsViaRequire.compile('Author: {{loud author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: YEHUDA');
assertEquals(template({ author: 'Yehuda' }), 'Author: YEHUDA');
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Handlebars from 'handlebars/dist/handlebars';

import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';

const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Handlebars from 'handlebars';
import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';

const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
6 changes: 3 additions & 3 deletions integration-testing/webpack-test/src/lib/assert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
26 changes: 12 additions & 14 deletions integration-testing/webpack-test/webpack.config.js
Original file line number Diff line number Diff line change
@@ -3,20 +3,18 @@ const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});

module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{test: /\.handlebars$/, loader: 'handlebars-loader'}
]
}
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }]
}
};
6 changes: 5 additions & 1 deletion lib/handlebars.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,11 @@ 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,
parseWithoutProcessing
} from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor';
30 changes: 22 additions & 8 deletions lib/handlebars/base.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {createFrame, extend, toString} from './utils';
import { createFrame, extend, toString } from './utils';
import Exception from './exception';
import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import { registerDefaultHelpers } from './helpers';
import { registerDefaultDecorators } from './decorators';
import logger from './logger';
import { resetLoggedProperties } from './internal/proto-access';

export const VERSION = '4.5.1';
export const VERSION = '4.7.4';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;

@@ -38,7 +39,9 @@ HandlebarsEnvironment.prototype = {

registerHelper: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
if (fn) {
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name);
} else {
this.helpers[name] = fn;
@@ -53,7 +56,9 @@ HandlebarsEnvironment.prototype = {
extend(this.partials, name);
} else {
if (typeof partial === 'undefined') {
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
throw new Exception(
`Attempting to register a partial called "${name}" as undefined`
);
}
this.partials[name] = partial;
}
@@ -64,17 +69,26 @@ HandlebarsEnvironment.prototype = {

registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
if (fn) {
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function(name) {
delete this.decorators[name];
},
/**
* Reset the memory of illegal property accesses that have already been logged.
* @deprecated should only be used in handlebars test-cases
*/
resetLoggedPropertyAccesses() {
resetLoggedProperties();
}
};

export let log = logger.log;

export {createFrame, logger};
export { createFrame, logger };
16 changes: 10 additions & 6 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
@@ -5,24 +5,28 @@ let AST = {
// * 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.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && 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;
4 changes: 3 additions & 1 deletion lib/handlebars/compiler/base.js
Original file line number Diff line number Diff line change
@@ -10,7 +10,9 @@ extend(yy, Helpers);

export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { return input; }
if (input.type === 'Program') {
return input;
}

parser.yy = yy;

49 changes: 26 additions & 23 deletions lib/handlebars/compiler/code-gen.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global define */
import {isArray} from '../utils';
import { isArray } from '../utils';

let SourceNode;

@@ -38,15 +38,14 @@ if (!SourceNode) {
this.src = chunks + this.src;
},
toStringWithSourceMap: function() {
return {code: this.toString()};
return { code: this.toString() };
},
toString: function() {
return this.src;
}
};
}


function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
@@ -62,7 +61,6 @@ function castChunk(chunk, codeGen, loc) {
return chunk;
}


function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
@@ -94,17 +92,22 @@ CodeGen.prototype = {
},

empty: function() {
let loc = this.currentLocation || {start: {}};
let loc = this.currentLocation || { start: {} };
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
},
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
if (chunk instanceof SourceNode) {
return chunk;
}

chunk = castChunk(chunk, this, loc);

return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
return new SourceNode(
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
},

functionCall: function(fn, type, params) {
@@ -113,34 +116,35 @@ 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 = [];

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
Object.keys(obj).forEach(key => {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
}
});

let ret = this.generateList(pairs);
ret.prepend('{');
ret.add('}');
return ret;
},


generateList: function(entries) {
let ret = this.empty();

@@ -165,4 +169,3 @@ CodeGen.prototype = {
};

export default CodeGen;

159 changes: 97 additions & 62 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable new-cap */

import Exception from '../exception';
import {isArray, indexOf, extend} from '../utils';
import { isArray, indexOf, extend } from '../utils';
import AST from './ast';

const slice = [].slice;
@@ -24,8 +24,11 @@ Compiler.prototype = {

for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
otherOpcode = other.opcodes[i];
if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false;
}
}
@@ -54,34 +57,28 @@ Compiler.prototype = {

options.blockParams = options.blockParams || [];

// These changes will propagate to the other compiler components
let knownHelpers = options.knownHelpers;
options.knownHelpers = {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
'if': true,
'unless': true,
'with': true,
'log': true,
'lookup': true
};
if (knownHelpers) {
// the next line should use "Object.keys", but the code has been like this a long time and changing it, might
// cause backwards-compatibility issues... It's an old library...
// eslint-disable-next-line guard-for-in
for (let name in knownHelpers) {
this.options.knownHelpers[name] = knownHelpers[name];
}
}
options.knownHelpers = extend(
Object.create(null),
{
helperMissing: true,
blockHelperMissing: true,
each: true,
if: true,
unless: true,
with: true,
log: true,
lookup: true
},
options.knownHelpers
);

return this.accept(program);
},

compileProgram: function(program) {
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;

@@ -107,7 +104,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]);
}
@@ -124,7 +121,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);
@@ -159,7 +156,7 @@ Compiler.prototype = {
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
path = decorator.path;

this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
@@ -175,17 +172,20 @@ Compiler.prototype = {

let params = partial.params;
if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
throw new Exception(
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) {
if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined');
} else {
params.push({type: 'PathExpression', parts: [], depth: 0});
params.push({ type: 'PathExpression', parts: [], depth: 0 });
}
}

let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
@@ -218,7 +218,6 @@ Compiler.prototype = {
this.DecoratorBlock(decorator);
},


ContentStatement: function(content) {
if (content.value) {
this.opcode('appendContent', content.value);
@@ -241,8 +240,8 @@ 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);

@@ -264,19 +263,27 @@ Compiler.prototype = {

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)
);
}
},

@@ -285,8 +292,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);
@@ -297,7 +304,13 @@ Compiler.prototype = {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
}
},

@@ -323,8 +336,8 @@ Compiler.prototype = {

Hash: function(hash) {
let pairs = hash.pairs,
i = 0,
l = pairs.length;
i = 0,
l = pairs.length;

this.opcode('pushHash');

@@ -339,7 +352,11 @@ 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) {
@@ -368,8 +385,7 @@ Compiler.prototype = {
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
let name = sexpr.path.parts[0],
options = this.options;

options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
@@ -397,9 +413,7 @@ Compiler.prototype = {

if (this.stringParams) {
if (value.replace) {
value = value
.replace(/^(\.?\.\/)*/g, '')
.replace(/\//g, '.');
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
}

if (val.depth) {
@@ -417,7 +431,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('.');
@@ -426,9 +440,9 @@ Compiler.prototype = {
value = val.original || value;
if (value.replace) {
value = value
.replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '')
.replace(/^\.$/, '');
.replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '')
.replace(/^\.$/, '');
}

this.opcode('pushId', val.type, value);
@@ -455,9 +469,13 @@ 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];
}
@@ -466,8 +484,14 @@ 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 || {};
@@ -479,13 +503,19 @@ 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);
@@ -500,8 +530,13 @@ 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);
}

49 changes: 29 additions & 20 deletions lib/handlebars/compiler/helpers.js
Original file line number Diff line number Diff line change
@@ -4,9 +4,12 @@ function validateClose(open, close) {
close = close.path ? close.path.original : close;

if (open.path.original !== close) {
let errorNode = {loc: open.path.loc};
let errorNode = { loc: open.path.loc };

throw new Exception(open.path.original + " doesn't match " + close, errorNode);
throw new Exception(
open.path.original + " doesn't match " + close,
errorNode
);
}
}

@@ -38,27 +41,26 @@ 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);

let original = data ? '@' : '',
dig = [],
depth = 0;
dig = [],
depth = 0;

for (let i = 0, l = parts.length; i < l; i++) {
let part = parts[i].part,
// If we have [] syntax then we do not treat path references as operators,
// i.e. foo.[this] resolves to approximately context.foo['this']
isLiteral = parts[i].original !== part;
// 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 });
} else if (part === '..') {
depth++;
}
@@ -80,9 +82,9 @@ export function preparePath(data, parts, loc) {
export function prepareMustache(path, params, hash, open, strip, locInfo) {
// Must use charAt to support IE pre-10
let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
escaped = escapeFlag !== '{' && escapeFlag !== '&';

let decorator = (/\*/.test(open));
let decorator = /\*/.test(open);
return {
type: decorator ? 'Decorator' : 'MustacheStatement',
path,
@@ -118,21 +120,30 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
};
}

export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
export function prepareBlock(
openBlock,
program,
inverseAndProgram,
close,
inverted,
locInfo
) {
if (close && close.path) {
validateClose(openBlock, close);
}

let decorator = (/\*/.test(openBlock.open));
let decorator = /\*/.test(openBlock.open);

program.blockParams = openBlock.blockParams;

let inverse,
inverseStrip;
let inverse, inverseStrip;

if (inverseAndProgram) {
if (decorator) {
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
throw new Exception(
'Unexpected inverse block on decorator',
inverseAndProgram
);
}

if (inverseAndProgram.chain) {
@@ -166,7 +177,7 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
export function prepareProgram(statements, loc) {
if (!loc && statements.length) {
const firstLoc = statements[0].loc,
lastLoc = statements[statements.length - 1].loc;
lastLoc = statements[statements.length - 1].loc;

/* istanbul ignore else */
if (firstLoc && lastLoc) {
@@ -192,7 +203,6 @@ export function prepareProgram(statements, loc) {
};
}


export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close);

@@ -207,4 +217,3 @@ export function preparePartialBlock(open, program, close, locInfo) {
loc: this.locInfo(locInfo)
};
}

335 changes: 231 additions & 104 deletions lib/handlebars/compiler/javascript-compiler.js

Large diffs are not rendered by default.

31 changes: 19 additions & 12 deletions lib/handlebars/compiler/printer.js
Original file line number Diff line number Diff line change
@@ -24,13 +24,14 @@ PrintVisitor.prototype.pad = function(string) {

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);
@@ -52,11 +53,14 @@ PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
};

PrintVisitor.prototype.BlockStatement =
PrintVisitor.prototype.DecoratorBlock = function(block) {
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
block
) {
let out = '';

out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
out += this.pad(
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++;
out += this.pad(this.SubExpression(block));
if (block.program) {
@@ -66,12 +70,16 @@ PrintVisitor.prototype.DecoratorBlock = function(block) {
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
if (block.program) {
this.padding++;
}
out += this.pad('{{^}}');
this.padding++;
out += this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
if (block.program) {
this.padding--;
}
}
this.padding--;

@@ -115,8 +123,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]));
@@ -134,7 +142,6 @@ PrintVisitor.prototype.PathExpression = function(id) {
return (id.data ? '@' : '') + 'PATH:' + path;
};


PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"';
};
@@ -157,7 +164,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]));
9 changes: 8 additions & 1 deletion lib/handlebars/compiler/visitor.js
Original file line number Diff line number Diff line change
@@ -15,7 +15,14 @@ Visitor.prototype = {
// 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);
throw new Exception(
'Unexpected node type "' +
value.type +
'" found when accepting ' +
name +
' on ' +
node.type
);
}
node[name] = value;
}
76 changes: 47 additions & 29 deletions lib/handlebars/compiler/whitespace-control.js
Original file line number Diff line number Diff line change
@@ -14,18 +14,18 @@ WhitespaceControl.prototype.Program = function(program) {
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);
@@ -41,7 +41,7 @@ WhitespaceControl.prototype.Program = function(program) {
// 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];
}
}
}
@@ -62,17 +62,17 @@ WhitespaceControl.prototype.Program = function(program) {
return program;
};

WhitespaceControl.prototype.BlockStatement =
WhitespaceControl.prototype.DecoratorBlock =
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
block
) {
this.accept(block.program);
this.accept(block.inverse);

// Find the inverse program that is involed with whitespace stripping.
let program = block.program || block.inverse,
inverse = block.program && block.inverse,
firstInverse = inverse,
lastInverse = inverse;
inverse = block.program && block.inverse,
firstInverse = inverse,
lastInverse = inverse;

if (inverse && inverse.chained) {
firstInverse = inverse.body[0].program;
@@ -112,9 +112,11 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
}

// Find standalone else statments
if (!this.options.ignoreStandalone
&& isPrevWhitespace(program.body)
&& isNextWhitespace(firstInverse.body)) {
if (
!this.options.ignoreStandalone &&
isPrevWhitespace(program.body) &&
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body);
omitRight(firstInverse.body);
}
@@ -125,13 +127,15 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
return strip;
};

WhitespaceControl.prototype.Decorator =
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
WhitespaceControl.prototype.Decorator = 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 {
@@ -141,7 +145,6 @@ WhitespaceControl.prototype.PartialStatement =
};
};


function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) {
i = body.length;
@@ -150,13 +153,15 @@ 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) {
@@ -165,13 +170,15 @@ 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
);
}
}

@@ -184,12 +191,19 @@ 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;
}

@@ -202,13 +216,17 @@ 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.
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 change: 0 additions & 1 deletion lib/handlebars/decorators.js
Original file line number Diff line number Diff line change
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}

2 changes: 1 addition & 1 deletion lib/handlebars/decorators/inline.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {extend} from '../utils';
import { extend } from '../utils';

export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) {
20 changes: 14 additions & 6 deletions lib/handlebars/exception.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@

const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
const errorProps = [
'description',
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];

function Exception(message, node) {
let loc = node && node.loc,
line,
endLineNumber,
column,
endColumn;
line,
endLineNumber,
column,
endColumn;

if (loc) {
line = loc.start.line;
11 changes: 7 additions & 4 deletions lib/handlebars/helpers/block-helper-missing.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {appendContextPath, createFrame, isArray} from '../utils';
import { appendContextPath, createFrame, isArray } from '../utils';

export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) {
let inverse = options.inverse,
fn = options.fn;
fn = options.fn;

if (context === true) {
return fn(this);
@@ -22,8 +22,11 @@ export default function(instance) {
} else {
if (options.data && options.ids) {
let data = createFrame(options.data);
data.contextPath = appendContextPath(options.data.contextPath, options.name);
options = {data: data};
data.contextPath = appendContextPath(
options.data.contextPath,
options.name
);
options = { data: data };
}

return fn(context, options);
58 changes: 35 additions & 23 deletions lib/handlebars/helpers/each.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
import {
appendContextPath,
blockParams,
createFrame,
isArray,
isFunction
} from '../utils';
import Exception from '../exception';

export default function(instance) {
@@ -8,17 +14,20 @@ export default function(instance) {
}

let fn = options.fn,
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;

if (options.data && options.ids) {
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
contextPath =
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}

if (isFunction(context)) { context = context.call(this); }
if (isFunction(context)) {
context = context.call(this);
}

if (options.data) {
data = createFrame(options.data);
@@ -36,10 +45,15 @@ export default function(instance) {
}
}

ret = ret + fn(context[field], {
data: data,
blockParams: blockParams([context[field], field], [contextPath + field, null])
});
ret =
ret +
fn(context[field], {
data: data,
blockParams: blockParams(
[context[field], field],
[contextPath + field, null]
)
});
}

if (context && typeof context === 'object') {
@@ -62,18 +76,16 @@ export default function(instance) {
} else {
let priorKey;

for (let key in context) {
if (context.hasOwnProperty(key)) {
// We're running the iterations one step out of sync so we can detect
// the last iteration without have to scan the object twice and create
// an itermediate keys array.
if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
priorKey = key;
i++;
Object.keys(context).forEach(key => {
// We're running the iterations one step out of sync so we can detect
// the last iteration without have to scan the object twice and create
// an itermediate keys array.
if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
}
priorKey = key;
i++;
});
if (priorKey !== undefined) {
execIteration(priorKey, i - 1, true);
}
4 changes: 3 additions & 1 deletion lib/handlebars/helpers/helper-missing.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@ export default function(instance) {
return undefined;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
throw new Exception(
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
}
});
}
18 changes: 14 additions & 4 deletions lib/handlebars/helpers/if.js
Original file line number Diff line number Diff line change
@@ -3,8 +3,12 @@ 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); }
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
@@ -17,7 +21,13 @@ export default function(instance) {
});

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});
if (arguments.length != 2) {
throw new Exception('#unless requires exactly one argument');
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
});
}
4 changes: 2 additions & 2 deletions lib/handlebars/helpers/log.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default function(instance) {
instance.registerHelper('log', function(/* message, options */) {
let args = [undefined],
options = arguments[arguments.length - 1];
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
@@ -14,6 +14,6 @@ export default function(instance) {
}
args[0] = level;

instance.log(... args);
instance.log(...args);
});
}
8 changes: 3 additions & 5 deletions lib/handlebars/helpers/lookup.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
export default function(instance) {
instance.registerHelper('lookup', function(obj, field) {
instance.registerHelper('lookup', function(obj, field, options) {
if (!obj) {
// Note for 5.0: Change to "obj == null" in 5.0
return obj;
}
if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
return undefined;
}
return obj[field];
return options.lookupProperty(obj, field);
});
}
21 changes: 17 additions & 4 deletions lib/handlebars/helpers/with.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { appendContextPath, blockParams, createFrame, isEmpty, isFunction } from '../utils';
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); }
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]);
data.contextPath = appendContextPath(
options.data.contextPath,
options.ids[0]
);
}

return fn(context, {
11 changes: 11 additions & 0 deletions lib/handlebars/internal/create-new-lookup-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { extend } from '../utils';

/**
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
* The resulting object can be used with "object[property]" to check if a property exists
* @param {...object} sources a varargs parameter of source objects that will be merged
* @returns {object}
*/
export function createNewLookupObject(...sources) {
return extend(Object.create(null), ...sources);
}
70 changes: 70 additions & 0 deletions lib/handlebars/internal/proto-access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createNewLookupObject } from './create-new-lookup-object';
import * as logger from '../logger';

const loggedProperties = Object.create(null);

export function createProtoAccessControl(runtimeOptions) {
let defaultMethodWhiteList = Object.create(null);
defaultMethodWhiteList['constructor'] = false;
defaultMethodWhiteList['__defineGetter__'] = false;
defaultMethodWhiteList['__defineSetter__'] = false;
defaultMethodWhiteList['__lookupGetter__'] = false;

let defaultPropertyWhiteList = Object.create(null);
// eslint-disable-next-line no-proto
defaultPropertyWhiteList['__proto__'] = false;

return {
properties: {
whitelist: createNewLookupObject(
defaultPropertyWhiteList,
runtimeOptions.allowedProtoProperties
),
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
},
methods: {
whitelist: createNewLookupObject(
defaultMethodWhiteList,
runtimeOptions.allowedProtoMethods
),
defaultValue: runtimeOptions.allowProtoMethodsByDefault
}
};
}

export function resultIsAllowed(result, protoAccessControl, propertyName) {
if (typeof result === 'function') {
return checkWhiteList(protoAccessControl.methods, propertyName);
} else {
return checkWhiteList(protoAccessControl.properties, propertyName);
}
}

function checkWhiteList(protoAccessControlForType, propertyName) {
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
return protoAccessControlForType.whitelist[propertyName] === true;
}
if (protoAccessControlForType.defaultValue !== undefined) {
return protoAccessControlForType.defaultValue;
}
logUnexpecedPropertyAccessOnce(propertyName);
return false;
}

function logUnexpecedPropertyAccessOnce(propertyName) {
if (loggedProperties[propertyName] !== true) {
loggedProperties[propertyName] = true;
logger.log(
'error',
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
`You can add a runtime option to disable the check or this warning:\n` +
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
);
}
}

export function resetLoggedProperties() {
Object.keys(loggedProperties).forEach(propertyName => {
delete loggedProperties[propertyName];
});
}
13 changes: 13 additions & 0 deletions lib/handlebars/internal/wrapHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function wrapHelper(helper, transformOptionsFn) {
if (typeof helper !== 'function') {
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
return helper;
}
let wrapper = function(/* dynamic arguments */) {
const options = arguments[arguments.length - 1];
arguments[arguments.length - 1] = transformOptionsFn(options);
return helper.apply(this, arguments);
};
return wrapper;
}
10 changes: 7 additions & 3 deletions lib/handlebars/logger.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {indexOf} from './utils';
import { indexOf } from './utils';

let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
@@ -22,9 +22,13 @@ let logger = {
log: function(level, ...message) {
level = logger.lookupLevel(level);

if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
if (
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level];
if (!console[method]) { // eslint-disable-line no-console
// eslint-disable-next-line no-console
if (!console[method]) {
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
3 changes: 1 addition & 2 deletions lib/handlebars/no-conflict.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* global window */
export default function(Handlebars) {
/* istanbul ignore next */
let root = typeof global !== 'undefined' ? global : window,
$Handlebars = root.Handlebars;
$Handlebars = root.Handlebars;
/* istanbul ignore next */
Handlebars.noConflict = function() {
if (root.Handlebars === Handlebars) {
Loading