Compare commits

...

26 Commits

Author SHA1 Message Date
Nils Knappmeier ad85b940e7 fix: "hash" is optional in AST 2019-11-01 22:50:58 +01:00
Nils Knappmeier 7ef86173ab v4.5.1 2019-10-29 05:42:23 +01:00
Nils Knappmeier b75e3e1f40 Update release notes 2019-10-29 05:41:43 +01:00
Nils Knappmeier 5e9d17f8fa fix: move "eslint-plugin-compat" to devDependencies 2019-10-29 05:17:03 +01:00
Nils Knappmeier b24797da01 v4.5.0 2019-10-28 19:47:51 +01:00
Nils Knappmeier a243067883 Update release notes 2019-10-28 19:47:08 +01:00
Nils Knappmeier 088e61812a chore: add eslint-plugin-compat and eslint-plugin-es5
- tests are not compiled with babel and must thus be in
  es5
- we don't use polyfills, so we need to make sure no
  functions aren't used that are not supported by popular browsers. (like Object.assign in Safari and IE11)
- Boths are errors that usually only appear when running
  tests in SauceLabs, which happens only on _after_
  merging a PR.
2019-10-28 19:17:57 +01:00
Kabir Baidhya 7052e88068 Resolve deprecation warning message from eslint while running eslint (#1586)
* Resolve eslint deprecation warning

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

For example, parsing the following template:

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

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

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

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

When the same template is parsed with
`Handlebars.parseWithoutProcessing` none of those modifications to the
AST are made. This enables "codemod tooling" (e.g. `prettier` and
`ember-template-recast`) to preserve the **exact** initial formatting.
Prior to these changes, those tools would have to _manually_ reconstruct
the whitespace that is lost prior to emitting source.
2019-10-28 00:28:28 +01:00
kpdecker 7fcf9d24f8 Use objects for hash value tracking
The use of arrays was incorrect for the data type and causing problems when hash keys conflicted with array behaviors.

Fixes #1194

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

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

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

closes #1579
2019-10-20 22:56:24 +02:00
Nils Knappmeier 2ab261eab7 v4.4.4 2019-10-20 21:33:00 +02:00
Nils Knappmeier 143ac806da Update release notes 2019-10-20 21:32:09 +02:00
Nils Knappmeier f1752fe66f fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:24:55 +02:00
Nils Knappmeier aaab6099f2 fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:23:02 +02:00
sohibe feb60f85c9 show source location for the strict lookup exceptions 2019-10-09 06:50:04 +02:00
Nils Knappmeier 0b593bfe5d chore: link to s3 bucket with https, add "npm ci" to build instructions 2019-10-08 22:19:01 +02:00
Nils Knappmeier 2e53fba68f v4.4.3 2019-10-08 22:05:35 +02:00
Nils Knappmeier ba570c42cf Update release notes 2019-10-08 22:04:57 +02:00
Alexander Schramm 0440af2147 added missing type fields in typings and tests for them 2019-10-08 19:44:06 +02:00
29 changed files with 6536 additions and 66 deletions
+7 -19
View File
@@ -1,25 +1,11 @@
module.exports = {
"extends": "eslint:recommended",
"extends": ["eslint:recommended","plugin:compat/recommended"],
"globals": {
"self": false
},
"env": {
"node": true
},
"ecmaFeatures": {
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
"arrowFunctions": true,
"blockBindings": true,
"defaultParams": true,
"destructuring": true,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"restParams": true,
"spread": true,
"templateStrings": true
"node": true,
"es6": true
},
"rules": {
// overrides eslint:recommended defaults
@@ -124,6 +110,8 @@ module.exports = {
"no-var": "warn"
},
"parserOptions": {
"sourceType": "module"
"sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {}
}
}
}
+2 -1
View File
@@ -90,6 +90,7 @@ Handlebars utilizes the [release yeoman generator][generator-release] to perform
A full release may be completed with the following:
```
npm ci
yo release
npm publish
yo release:publish components handlebars.js dist/components/
@@ -104,7 +105,7 @@ in those places still point to the latest version
* [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
* [The bower package](https://github.com/components/handlebars.js) (check the package.json)
* [The AWS S3 Bucket](http://builds.handlebarsjs.com.s3.amazonaws.com/) (check latest-tag)
* [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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.4.2",
"version": "4.5.1",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
+1 -1
View File
@@ -2,7 +2,7 @@
<package>
<metadata>
<id>handlebars.js</id>
<version>4.4.2</version>
<version>4.5.1</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "handlebars",
"version": "4.4.2",
"version": "4.5.1",
"license": "MIT",
"jspm": {
"main": "handlebars",
+28
View File
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
Handlebars.precompile(ast);
```
### Parsing
There are two primary APIs that are used to parse an existing template into the AST:
#### parseWithoutProcessing
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
Example:
```js
let ast = Handlebars.parseWithoutProcessing(myTemplate);
```
#### parse
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
Example:
```js
let ast = Handlebars.parse(myTemplate);
```
### Basic
+2 -1
View File
@@ -2,7 +2,7 @@ import runtime from './handlebars.runtime';
// Compiler imports
import AST from './handlebars/compiler/ast';
import { parser as Parser, parse } from './handlebars/compiler/base';
import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor';
@@ -25,6 +25,7 @@ function create() {
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;
return hb;
}
+1 -1
View File
@@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import logger from './logger';
export const VERSION = '4.4.2';
export const VERSION = '4.5.1';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
+11 -3
View File
@@ -8,7 +8,7 @@ export { parser };
let yy = {};
extend(yy, Helpers);
export function parse(input, options) {
export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { return input; }
@@ -19,6 +19,14 @@ export function parse(input, options) {
return new yy.SourceLocation(options && options.srcName, locInfo);
};
let strip = new WhitespaceControl(options);
return strip.accept(parser.parse(input));
let ast = parser.parse(input);
return ast;
}
export function parse(input, options) {
let ast = parseWithoutProcessing(input, options);
let strip = new WhitespaceControl(options);
return strip.accept(ast);
}
@@ -543,7 +543,7 @@ JavaScriptCompiler.prototype = {
if (this.hash) {
this.hashes.push(this.hash);
}
this.hash = {values: [], types: [], contexts: [], ids: []};
this.hash = {values: {}, types: [], contexts: [], ids: []};
},
popHash: function() {
let hash = this.hash;
@@ -1091,6 +1091,7 @@ JavaScriptCompiler.prototype = {
setupHelperArgs: function(helper, paramSize, params, useRegister) {
let options = this.setupParams(helper, paramSize, params);
options.loc = JSON.stringify(this.source.currentLocation);
options = this.objectLiteral(options);
if (useRegister) {
this.useRegister('options');
@@ -1150,7 +1151,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
}
if (requireTerminal) {
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )'];
} else {
return stack;
}
+13 -2
View File
@@ -1,13 +1,18 @@
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
let loc = node && node.loc,
line,
column;
endLineNumber,
column,
endColumn;
if (loc) {
line = loc.start.line;
endLineNumber = loc.end.line;
column = loc.start.column;
endColumn = loc.end.column;
message += ' - ' + line + ':' + column;
}
@@ -27,6 +32,7 @@ function Exception(message, node) {
try {
if (loc) {
this.lineNumber = line;
this.endLineNumber = endLineNumber;
// Work around issue under safari where we can't directly set the column value
/* istanbul ignore next */
@@ -35,8 +41,13 @@ function Exception(message, node) {
value: column,
enumerable: true
});
Object.defineProperty(this, 'endColumn', {
value: endColumn,
enumerable: true
});
} else {
this.column = column;
this.endColumn = endColumn;
}
}
} catch (nop) {
+4 -1
View File
@@ -1,7 +1,9 @@
import {isEmpty, isFunction} from '../utils';
import { isEmpty, isFunction } from '../utils';
import Exception from '../exception';
export default function(instance) {
instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');}
if (isFunction(conditional)) { conditional = conditional.call(this); }
// Default behavior is to render the positive path if the value is truthy and not empty.
@@ -15,6 +17,7 @@ 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});
});
}
+3 -1
View File
@@ -1,7 +1,9 @@
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); }
let fn = options.fn;
+3 -3
View File
@@ -79,9 +79,9 @@ export function template(templateSpec, env) {
// Just add water
let container = {
strict: function(obj, name) {
if (!(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj);
strict: function(obj, name, loc) {
if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, { loc: loc });
}
return obj[name];
},
+112 -1
View File
@@ -1,9 +1,18 @@
{
"name": "handlebars",
"version": "4.4.1",
"version": "4.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"@knappi/grunt-saucelabs": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@knappi/grunt-saucelabs/-/grunt-saucelabs-9.0.2.tgz",
@@ -321,6 +330,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"ast-metadata-inferer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.1.1.tgz",
"integrity": "sha512-hc9w8Qrgg9Lf9iFcZVhNjUnhrd2BBpTlyCnegPVvCe6O0yMrF57a6Cmh7k+xUsfUOMh9wajOL5AsGOBNEyTCcw==",
"dev": true
},
"ast-traverse": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz",
@@ -832,6 +847,17 @@
"pako": "~0.2.0"
}
},
"browserslist": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.2.tgz",
"integrity": "sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001004",
"electron-to-chromium": "^1.3.295",
"node-releases": "^1.1.38"
}
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
@@ -931,6 +957,18 @@
}
}
},
"caniuse-db": {
"version": "1.0.30001005",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001005.tgz",
"integrity": "sha512-MSRfm2N6FRDSpAJ00ipCuFe0CNink5JJOFzl4S7fLSBJdowhGq3uMxzkWGTjvvReo1PuWfK5YYJydJJ+9mJebw==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001005",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001005.tgz",
"integrity": "sha512-g78miZm1Z5njjYR216a5812oPiLgV1ssndgGxITHWUopmjUrCswMisA0a2kSB7a0vZRox6JOKhM51+efmYN8Mg==",
"dev": true
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -1747,6 +1785,12 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.296",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.296.tgz",
"integrity": "sha512-s5hv+TSJSVRsxH190De66YHb50pBGTweT9XGWYu/LMR20KX6TsjFzObo36CjVAzM+PUeeKSBRtm/mISlCzeojQ==",
"dev": true
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -2067,6 +2111,35 @@
}
}
},
"eslint-plugin-compat": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-3.3.0.tgz",
"integrity": "sha512-QCgYy3pZ+zH10dkBJus1xER0359h1UhJjufhQRqp9Owm6BEoLZeSqxf2zINwL1OGao9Yc96xPYIW3nQj5HUryg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.5",
"ast-metadata-inferer": "^0.1.1",
"browserslist": "^4.6.3",
"caniuse-db": "^1.0.30000977",
"lodash.memoize": "4.1.2",
"mdn-browser-compat-data": "^0.0.84",
"semver": "^6.1.2"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"eslint-plugin-es5": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-es5/-/eslint-plugin-es5-1.4.1.tgz",
"integrity": "sha512-kktkmkF2O7pnSZYgrMiYMbt3wCKRIiXePwILv8USDG95YgP0PzhIxSIROLLKmiQQ/Z6LuhDGWTHK04gnbXBvkg==",
"dev": true
},
"eslint-scope": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
@@ -5129,6 +5202,12 @@
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@@ -5220,6 +5299,15 @@
"pretty-bytes": "^1.0.0"
}
},
"mdn-browser-compat-data": {
"version": "0.0.84",
"resolved": "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.84.tgz",
"integrity": "sha512-fAznuGNaQMQiWLVf+gyp33FaABTglYWqMT7JqvH+4RZn2UQPD12gbMqxwP9m0lj8AAbNpu5/kD6n4Ox1SOffpw==",
"dev": true,
"requires": {
"extend": "3.0.2"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5622,6 +5710,23 @@
}
}
},
"node-releases": {
"version": "1.1.39",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.39.tgz",
"integrity": "sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==",
"dev": true,
"requires": {
"semver": "^6.3.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"nomnom": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-0.4.3.tgz",
@@ -6746,6 +6851,12 @@
"through": "~2.3.8"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
"dev": true
},
"regex-cache": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+3 -1
View File
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"barename": "handlebars",
"version": "4.4.2",
"version": "4.5.1",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
@@ -37,6 +37,8 @@
"dtslint": "^0.5.5",
"dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3",
"eslint-plugin-compat": "^3.3.0",
"eslint-plugin-es5": "^1.4.1",
"grunt": "^1.0.3",
"grunt-babel": "^5.0.0",
"grunt-bg-shell": "^2.3.3",
+61 -1
View File
@@ -2,7 +2,67 @@
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.2...master)
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.1...master)
## v4.5.1 - October 29th, 2019
Bugfixs
- fix: move "eslint-plugin-compat" to devDependencies - 5e9d17f (#1589)
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.0...v4.5.1)
## v4.5.0 - October 28th, 2019
Features / Improvements
- Add method Handlebars.parseWithoutProcessing (#1584) - 62ed3c2
- add guard to if & unless helpers (#1549)
- show source location for the strict lookup exceptions - feb60f8
Bugfixes:
- Use objects for hash value tracking - 7fcf9d2
Chore:
- Resolve deprecation warning message from eslint while running eslint (#1586) - 7052e88
- chore: add eslint-plugin-compat and eslint-plugin-es5 - 088e618
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...v4.5.0)
## v4.4.5 - October 20th, 2019
Bugfixes:
- Contents of raw-blocks must be matched with non-eager regex-matching - 8d5530e, #1579
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.4...v4.4.5)
## v4.4.4 - October 20th, 2019
Bugfixes:
- fix: prevent zero length tokens in raw-blocks (#1577, #1578) - f1752fe
Chore:
- chore: link to s3 bucket with https, add "npm ci" to build instructions - 0b593bf
Compatibility notes:
- no compatibility issues are expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.3...v4.4.4)
## v4.4.3 - October 8th, 2019
Bugfixes
Typings:
- add missing type fields to AST typings and add tests for them - 0440af2
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.2...v4.4.3)
## v4.4.2 - October 2nd, 2019
- chore: fix grunt-saucelabs dependency - b7eada0
+7 -2
View File
@@ -1,14 +1,19 @@
{
"extends": [
"../.eslintrc.js",
"plugin:es5/no-es2015"
],
"plugins": [
"es5"
],
"globals": {
"CompilerContext": true,
"Handlebars": true,
"handlebarsEnv": true,
"shouldCompileTo": true,
"shouldCompileToWithPartials": true,
"shouldThrow": true,
"compileWithPartials": true,
"console": true,
"require": true,
"suite": true,
+105
View File
@@ -123,6 +123,40 @@ describe('ast', function() {
});
});
describe('whitespace control', function() {
describe('parse', function() {
it('mustache', function() {
var ast = Handlebars.parse(' {{~comment~}} ');
equals(ast.body[0].value, '');
equals(ast.body[2].value, '');
});
it('block statements', function() {
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
equals(ast.body[0].value, '');
equals(ast.body[1].program.body[0].value, 'foo');
});
});
describe('parseWithoutProcessing', function() {
it('mustache', function() {
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment~}} \nfoo\n {{~/comment}}');
equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
});
});
});
describe('standalone flags', function() {
describe('mustache', function() {
it('does not mark mustaches as standalone', function() {
@@ -131,6 +165,54 @@ describe('ast', function() {
equals(!!ast.body[2].value, true);
});
});
describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
equals(ast.body[0].value, ' ');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n ');
});
it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, ' \n');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n ');
});
it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, ' \n');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
});
it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
});
describe('blocks', function() {
it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
@@ -204,6 +286,18 @@ describe('ast', function() {
equals(ast.body[2].value, '');
});
});
describe('partials - parseWithoutProcessing', function() {
it('simple partial', function() {
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
equals(ast.body[1].value, ' ');
});
it('indented partial', function() {
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[1].indent, '');
equals(ast.body[2].value, ' ');
});
});
describe('partials', function() {
it('marks partial as standalone', function() {
var ast = Handlebars.parse('{{> partial }} ');
@@ -223,6 +317,17 @@ describe('ast', function() {
equals(ast.body[1].omit, undefined);
});
});
describe('comments - parseWithoutProcessing', function() {
it('simple comment', function() {
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
equals(ast.body[1].value, ' ');
});
it('indented comment', function() {
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
});
describe('comments', function() {
it('marks comment as standalone', function() {
var ast = Handlebars.parse('{{! comment }} ');
+1 -1
View File
@@ -9,7 +9,7 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
+102 -7
View File
@@ -28,14 +28,43 @@ describe('helpers', function() {
'raw block helper gets raw content');
});
it('helper for nested raw block gets raw content', function() {
var string = '{{{{a}}}} {{{{b}}}} {{{{/b}}}} {{{{/a}}}}';
var helpers = {
a: function(options) {
describe('raw block parsing (with identity helper-function)', function() {
function runWithIdentityHelper(template, expected) {
var helpers = {
identity: function(options) {
return options.fn();
}
};
shouldCompileTo(string, [{}, helpers], ' {{{{b}}}} {{{{/b}}}} ', 'raw block helper should get nested raw block as raw content');
}
};
shouldCompileTo(template, [{}, helpers], expected);
}
it('helper for nested raw block gets raw content', function() {
runWithIdentityHelper('{{{{identity}}}} {{{{b}}}} {{{{/b}}}} {{{{/identity}}}}', ' {{{{b}}}} {{{{/b}}}} ');
});
it('helper for nested raw block works with empty content', function() {
runWithIdentityHelper('{{{{identity}}}}{{{{/identity}}}}', '');
});
xit('helper for nested raw block works if nested raw blocks are broken', function() {
// This test was introduced in 4.4.4, but it was not the actual problem that lead to the patch release
// The test is deactivated, because in 3.x this template cases an exception and it also does not work in 4.4.3
// If anyone can make this template work without breaking everything else, then go for it,
// but for now, this is just a known bug, that will be documented.
runWithIdentityHelper('{{{{identity}}}} {{{{a}}}} {{{{ {{{{/ }}}} }}}} {{{{/identity}}}}', ' {{{{a}}}} {{{{ {{{{/ }}}} }}}} ');
});
it('helper for nested raw block closes after first matching close', function() {
runWithIdentityHelper('{{{{identity}}}}abc{{{{/identity}}}} {{{{identity}}}}abc{{{{/identity}}}}', 'abc abc');
});
it('helper for nested raw block throw exception when with missing closing braces', function() {
var string = '{{{{a}}}} {{{{/a';
shouldThrow(function() {
Handlebars.compile(string)();
});
});
});
it('helper block with identical context', function() {
@@ -737,4 +766,70 @@ describe('helpers', function() {
shouldCompileTo('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}', [hash, helpers], '1foo');
});
});
describe('built-in helpers malformed arguments ', function() {
it('if helper - too few arguments', function() {
var template = CompilerContext.compile('{{#if}}{{/if}}');
shouldThrow(function() {
template({});
}, undefined, /#if requires exactly one argument/);
});
it('if helper - too many arguments, string', function() {
var template = CompilerContext.compile('{{#if test "string"}}{{/if}}');
shouldThrow(function() {
template({});
}, undefined, /#if requires exactly one argument/);
});
it('if helper - too many arguments, undefined', function() {
var template = CompilerContext.compile('{{#if test undefined}}{{/if}}');
shouldThrow(function() {
template({});
}, undefined, /#if requires exactly one argument/);
});
it('if helper - too many arguments, null', function() {
var template = CompilerContext.compile('{{#if test null}}{{/if}}');
shouldThrow(function() {
template({});
}, undefined, /#if requires exactly one argument/);
});
it('unless helper - too few arguments', function() {
var template = CompilerContext.compile('{{#unless}}{{/unless}}');
shouldThrow(function() {
template({});
}, undefined, /#unless requires exactly one argument/);
});
it('unless helper - too many arguments', function() {
var template = CompilerContext.compile('{{#unless test null}}{{/unless}}');
shouldThrow(function() {
template({});
}, undefined, /#unless requires exactly one argument/);
});
it('with helper - too few arguments', function() {
var template = CompilerContext.compile('{{#with}}{{/with}}');
shouldThrow(function() {
template({});
}, undefined, /#with requires exactly one argument/);
});
it('with helper - too many arguments', function() {
var template = CompilerContext.compile('{{#with test "string"}}{{/with}}');
shouldThrow(function() {
template({});
}, undefined, /#with requires exactly one argument/);
});
});
});
+11
View File
@@ -323,4 +323,15 @@ describe('Regressions', function() {
}, 'useData': true});
}
});
it('should allow hash with protected array names', function() {
var obj = {array: [1], name: 'John'};
var helpers = {
helpa: function(options) {
return options.hash.length;
}
};
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
});
});
+19
View File
@@ -95,6 +95,25 @@ describe('strict', function() {
};
equals(template({}, {helpers: helpers}), 'success');
});
it('should show error location on missing property lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
template({});
}, Exception, '"hello" not defined in [object Object] - 4:5');
});
it('should error contains correct location properties on missing property lookup', function() {
try {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
template({});
} catch (error) {
equals(error.lineNumber, 4);
equals(error.endLineNumber, 4);
equals(error.column, 5);
equals(error.endColumn, 10);
}
});
});
describe('assume objects', function() {
+5
View File
@@ -441,4 +441,9 @@ describe('Tokenizer', function() {
result = tokenize('{{else foo as |bar baz|}}');
shouldMatchTokens(result, ['OPEN_INVERSE_CHAIN', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
});
it('tokenizes raw blocks', function() {
var result = tokenize('{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}');
shouldMatchTokens(result, ['OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK', 'CONTENT', 'OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK']);
});
});
+1 -1
View File
@@ -63,7 +63,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
return 'END_RAW_BLOCK';
}
}
<raw>[^\x00]*?/("{{{{") { return 'CONTENT'; }
<raw>[^\x00]+?/("{{{{") { return 'CONTENT'; }
<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
this.popState();
+1 -1
View File
@@ -39,7 +39,7 @@ content
};
rawBlock
: openRawBlock content+ END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
: openRawBlock content* END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
;
openRawBlock
+59 -13
View File
@@ -47,8 +47,8 @@ declare namespace Handlebars {
}
export interface ParseOptions {
srcName?: string,
ignoreStandalone?: boolean
srcName?: string;
ignoreStandalone?: boolean;
}
export function registerHelper(name: string, fn: HelperDelegate): void;
@@ -66,9 +66,9 @@ declare namespace Handlebars {
export function K(): void;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function Exception(message: string): void;
export function log(level: number, obj: any): void;
export function parse(input: string, options?: ParseOptions): hbs.AST.Program;
export function parseWithoutProcessing(input: string, options?: ParseOptions): hbs.AST.Program;
export function compile<T = any>(input: any, options?: CompileOptions): HandlebarsTemplateDelegate<T>;
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
export function template<T = any>(precompilation: TemplateSpecification): HandlebarsTemplateDelegate<T>;
@@ -86,6 +86,20 @@ declare namespace Handlebars {
export function noConflict(): typeof Handlebars;
export class Exception {
constructor(message: string, node?: hbs.AST.Node);
description: string;
fileName: string;
lineNumber?: any;
endLineNumber?: any;
message: string;
name: string;
number: number;
stack?: string;
column?: any;
endColumn?: any;
}
export class SafeString {
constructor(str: string);
toString(): string;
@@ -264,25 +278,33 @@ declare namespace hbs {
interface Program extends Node {
body: Statement[];
blockParams: string[];
}
interface Statement extends Node {}
interface MustacheStatement extends Statement {
type: 'MustacheStatement';
path: PathExpression | Literal;
params: Expression[];
hash: Hash;
hash?: Hash;
escaped: boolean;
strip: StripFlags;
}
interface Decorator extends MustacheStatement { }
interface Decorator extends Statement {
type: 'Decorator';
path: PathExpression | Literal;
params: Expression[];
hash?: Hash;
escaped: boolean;
strip: StripFlags;
}
interface BlockStatement extends Statement {
type: 'BlockStatement';
path: PathExpression;
params: Expression[];
hash: Hash;
hash?: Hash;
program: Program;
inverse: Program;
openStrip: StripFlags;
@@ -290,31 +312,43 @@ declare namespace hbs {
closeStrip: StripFlags;
}
interface DecoratorBlock extends BlockStatement { }
interface DecoratorBlock extends Statement {
type: 'DecoratorBlock';
path: PathExpression;
params: Expression[];
hash?: Hash;
program: Program;
openStrip: StripFlags;
closeStrip: StripFlags;
}
interface PartialStatement extends Statement {
type: 'PartialStatement';
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
hash?: Hash;
indent: string;
strip: StripFlags;
}
interface PartialBlockStatement extends Statement {
type: 'PartialBlockStatement';
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
hash?: Hash;
program: Program;
openStrip: StripFlags;
closeStrip: StripFlags;
}
interface ContentStatement extends Statement {
type: 'ContentStatement';
value: string;
original: StripFlags;
}
interface CommentStatement extends Statement {
type: 'CommentStatement';
value: string;
strip: StripFlags;
}
@@ -322,12 +356,14 @@ declare namespace hbs {
interface Expression extends Node {}
interface SubExpression extends Expression {
type: 'SubExpression';
path: PathExpression;
params: Expression[];
hash: Hash;
hash?: Hash;
}
interface PathExpression extends Expression {
type: 'PathExpression';
data: boolean;
depth: number;
parts: string[];
@@ -335,30 +371,40 @@ declare namespace hbs {
}
interface Literal extends Expression {}
interface StringLiteral extends Literal {
type: 'StringLiteral';
value: string;
original: string;
}
interface BooleanLiteral extends Literal {
type: 'BooleanLiteral';
value: boolean;
original: boolean;
}
interface NumberLiteral extends Literal {
type: 'NumberLiteral';
value: number;
original: number;
}
interface UndefinedLiteral extends Literal {}
interface UndefinedLiteral extends Literal {
type: 'UndefinedLiteral';
}
interface NullLiteral extends Literal {}
interface NullLiteral extends Literal {
type: 'NullLiteral';
}
interface Hash extends Node {
type: 'Hash';
pairs: HashPair[];
}
interface HashPair extends Node {
type: 'HashPair';
key: string;
value: Expression;
}
+130 -1
View File
@@ -110,4 +110,133 @@ Handlebars.compile('test', {
Handlebars.compile('test')({},{allowCallsToHelperMissing: true});
Handlebars.compile('test')({},{});
Handlebars.compile('test')({},{});
const allthings = {} as hbs.AST.MustacheStatement |
hbs.AST.BlockStatement |
hbs.AST.PartialStatement |
hbs.AST.PartialBlockStatement |
hbs.AST.ContentStatement |
hbs.AST.CommentStatement |
hbs.AST.SubExpression |
hbs.AST.PathExpression |
hbs.AST.StringLiteral |
hbs.AST.BooleanLiteral |
hbs.AST.NumberLiteral |
hbs.AST.UndefinedLiteral |
hbs.AST.NullLiteral |
hbs.AST.Hash |
hbs.AST.HashPair;
switch(allthings.type) {
case "MustacheStatement":
let mustacheStatement: hbs.AST.MustacheStatement;
mustacheStatement = allthings;
break;
case "BlockStatement":
let blockStatement: hbs.AST.BlockStatement;
blockStatement = allthings;
break;
case "PartialStatement":
let partialStatement: hbs.AST.PartialStatement;
partialStatement = allthings;
break;
case "PartialBlockStatement":
let partialBlockStatement: hbs.AST.PartialBlockStatement;
partialBlockStatement = allthings;
break;
case "ContentStatement":
let ContentStatement: hbs.AST.ContentStatement;
ContentStatement = allthings;
break;
case "CommentStatement":
let CommentStatement: hbs.AST.CommentStatement;
CommentStatement = allthings;
break;
case "SubExpression":
let SubExpression: hbs.AST.SubExpression;
SubExpression = allthings;
break;
case "PathExpression":
let PathExpression: hbs.AST.PathExpression;
PathExpression = allthings;
break;
case "StringLiteral":
let StringLiteral: hbs.AST.StringLiteral;
StringLiteral = allthings;
break;
case "BooleanLiteral":
let BooleanLiteral: hbs.AST.BooleanLiteral;
BooleanLiteral = allthings;
break;
case "NumberLiteral":
let NumberLiteral: hbs.AST.NumberLiteral;
NumberLiteral = allthings;
break;
case "UndefinedLiteral":
let UndefinedLiteral: hbs.AST.UndefinedLiteral;
UndefinedLiteral = allthings;
break;
case "NullLiteral":
let NullLiteral: hbs.AST.NullLiteral;
NullLiteral = allthings;
break;
case "Hash":
let Hash: hbs.AST.Hash;
Hash = allthings;
break;
case "HashPair":
let HashPair: hbs.AST.HashPair;
HashPair = allthings;
break;
default:
break;
}
function testParseWithoutProcessing() {
const parsedTemplate: hbs.AST.Program = Handlebars.parseWithoutProcessing('<p>Hello, my name is {{name}}.</p>', {
srcName: "/foo/bar/baz.hbs",
});
const parsedTemplateWithoutOptions: hbs.AST.Program = Handlebars.parseWithoutProcessing('<p>Hello, my name is {{name}}.</p>');
}
function testExceptionTypings() {
// Test exception constructor with a single argument - message.
let exception: Handlebars.Exception = new Handlebars.Exception('message');
// Fields
let message: string = exception.message;
let lineNumber: number = exception.lineNumber;
let column: number = exception.column;
let endLineNumber: number = exception.endLineNumber;
let endColumn: number = exception.endColumn;
let description = exception.description;
let name: string = exception.name;
let fileName: string = exception.fileName;
let stack: string | undefined = exception.stack;
}
function testExceptionWithNodeTypings() {
// Test exception constructor with both arguments.
const exception: Handlebars.Exception = new Handlebars.Exception('message', {
type: 'MustacheStatement',
loc: {
source: 'source',
start: { line: 1, column: 5 },
end: { line: 10, column: 2 }
}
});
// Fields
let message: string = exception.message;
let lineNumber: number = exception.lineNumber;
let column: number = exception.column;
let endLineNumber: number = exception.endLineNumber;
let endColumn: number = exception.endColumn;
let description = exception.description;
let name: string = exception.name;
let fileName: string = exception.fileName;
let stack: string | undefined = exception.stack;
}
+5839
View File
File diff suppressed because it is too large Load Diff