Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51892c296e | |||
| c5b699db00 | |||
| ee4dd7d4bf | |||
| 4801f649df | |||
| 9888569dcb | |||
| 1ade5a0892 | |||
| c819c8b533 | |||
| 827c9d0747 | |||
| f7f05d7558 | |||
| 1988878087 | |||
| 886ba86c2f | |||
| 0817dad7e7 | |||
| 93516a0b07 | |||
| 93e284ed9b | |||
| c02b05fa81 | |||
| 8de121d21c | |||
| 6914090086 | |||
| d54137810a | |||
| c2ac79c970 | |||
| 7ef86173ab | |||
| b75e3e1f40 | |||
| 5e9d17f8fa | |||
| b24797da01 | |||
| a243067883 | |||
| 088e61812a | |||
| 7052e88068 | |||
| b8913fcc65 | |||
| 62ed3c25c7 | |||
| 7fcf9d24f8 | |||
| c76ded8f0f | |||
| a15d01d383 | |||
| 8e1cce7918 | |||
| def79fd5d6 | |||
| fd2e5c983d | |||
| 8d5530ee2c | |||
| 2ab261eab7 | |||
| 143ac806da | |||
| f1752fe66f | |||
| aaab6099f2 | |||
| feb60f85c9 | |||
| 0b593bfe5d | |||
| 2e53fba68f | |||
| ba570c42cf | |||
| 0440af2147 | |||
| b793350fec | |||
| 26d0f7a80d | |||
| b7eada0149 | |||
| b8e769fcb6 | |||
| 9cb31653a4 | |||
| c53f340374 | |||
| 059b330579 | |||
| 8f6047cdfd | |||
| cf7545ef5a |
@@ -0,0 +1,2 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
+10
-20
@@ -1,25 +1,12 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"extends": "eslint:recommended",
|
"extends": ["eslint:recommended","plugin:compat/recommended"],
|
||||||
"globals": {
|
"globals": {
|
||||||
"self": false
|
"self": false,
|
||||||
|
"SOURCE_MAPS": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"node": true
|
"node": true,
|
||||||
},
|
"es6": true
|
||||||
"ecmaFeatures": {
|
|
||||||
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
|
|
||||||
"arrowFunctions": true,
|
|
||||||
"blockBindings": true,
|
|
||||||
"defaultParams": true,
|
|
||||||
"destructuring": true,
|
|
||||||
"modules": true,
|
|
||||||
"objectLiteralComputedProperties": true,
|
|
||||||
"objectLiteralDuplicateProperties": true,
|
|
||||||
"objectLiteralShorthandMethods": true,
|
|
||||||
"objectLiteralShorthandProperties": true,
|
|
||||||
"restParams": true,
|
|
||||||
"spread": true,
|
|
||||||
"templateStrings": true
|
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
// overrides eslint:recommended defaults
|
// overrides eslint:recommended defaults
|
||||||
@@ -71,6 +58,7 @@ module.exports = {
|
|||||||
"no-with": "error",
|
"no-with": "error",
|
||||||
"radix": "error",
|
"radix": "error",
|
||||||
"wrap-iife": "error",
|
"wrap-iife": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
|
||||||
|
|
||||||
// Variables //
|
// Variables //
|
||||||
@@ -124,6 +112,8 @@ module.exports = {
|
|||||||
"no-var": "warn"
|
"no-var": "warn"
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module"
|
"sourceType": "module",
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"ecmaFeatures": {}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
+2
-1
@@ -90,6 +90,7 @@ Handlebars utilizes the [release yeoman generator][generator-release] to perform
|
|||||||
A full release may be completed with the following:
|
A full release may be completed with the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
npm ci
|
||||||
yo release
|
yo release
|
||||||
npm publish
|
npm publish
|
||||||
yo release:publish components handlebars.js dist/components/
|
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 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 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)
|
* [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.
|
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.
|
||||||
|
|||||||
+12
-75
@@ -43,16 +43,17 @@ module.exports = function(grunt) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
webpack: {
|
||||||
|
build: require('./webpack.config')
|
||||||
|
},
|
||||||
babel: {
|
babel: {
|
||||||
options: {
|
options: {
|
||||||
sourceMaps: 'inline',
|
sourceMaps: 'inline',
|
||||||
loose: ['es6.modules'],
|
|
||||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||||
},
|
},
|
||||||
amd: {
|
amd: {
|
||||||
options: {
|
options: {
|
||||||
modules: 'amd'
|
plugins: ['@babel/plugin-transform-modules-amd']
|
||||||
},
|
},
|
||||||
files: [{
|
files: [{
|
||||||
expand: true,
|
expand: true,
|
||||||
@@ -64,7 +65,11 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
cjs: {
|
cjs: {
|
||||||
options: {
|
options: {
|
||||||
modules: 'common'
|
plugins: [
|
||||||
|
'@babel/plugin-transform-modules-commonjs',
|
||||||
|
// support "import * as Handlebars" for backward-compatibility
|
||||||
|
'babel-plugin-add-module-exports'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
files: [{
|
files: [{
|
||||||
cwd: 'lib/',
|
cwd: 'lib/',
|
||||||
@@ -74,72 +79,6 @@ module.exports = function(grunt) {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webpack: {
|
|
||||||
options: {
|
|
||||||
context: __dirname,
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
|
||||||
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: 'dist/',
|
|
||||||
library: 'Handlebars',
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handlebars: {
|
|
||||||
entry: './lib/handlebars.js',
|
|
||||||
output: {
|
|
||||||
filename: 'handlebars.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runtime: {
|
|
||||||
entry: './lib/handlebars.runtime.js',
|
|
||||||
output: {
|
|
||||||
filename: 'handlebars.runtime.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
requirejs: {
|
|
||||||
options: {
|
|
||||||
optimize: 'none',
|
|
||||||
baseUrl: 'dist/amd/'
|
|
||||||
},
|
|
||||||
dist: {
|
|
||||||
options: {
|
|
||||||
name: 'handlebars',
|
|
||||||
out: 'dist/handlebars.amd.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runtime: {
|
|
||||||
options: {
|
|
||||||
name: 'handlebars.runtime',
|
|
||||||
out: 'dist/handlebars.runtime.amd.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uglify: {
|
|
||||||
options: {
|
|
||||||
mangle: true,
|
|
||||||
compress: true,
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
concat: {
|
concat: {
|
||||||
tests: {
|
tests: {
|
||||||
@@ -221,25 +160,23 @@ module.exports = function(grunt) {
|
|||||||
'node',
|
'node',
|
||||||
'globals']);
|
'globals']);
|
||||||
|
|
||||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
this.registerTask('amd', ['babel:amd']);
|
||||||
this.registerTask('node', ['babel:cjs']);
|
this.registerTask('node', ['babel:cjs']);
|
||||||
this.registerTask('globals', ['webpack']);
|
this.registerTask('globals', ['webpack']);
|
||||||
this.registerTask('tests', ['concat:tests']);
|
this.registerTask('tests', ['concat:tests']);
|
||||||
|
|
||||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||||
|
|
||||||
// Load tasks from npm
|
// Load tasks from npm
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-babel');
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
grunt.loadNpmTasks('grunt-bg-shell');
|
grunt.loadNpmTasks('grunt-bg-shell');
|
||||||
grunt.loadNpmTasks('grunt-eslint');
|
grunt.loadNpmTasks('grunt-eslint');
|
||||||
grunt.loadNpmTasks('grunt-saucelabs');
|
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
grunt.task.loadTasks('tasks');
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = function(api) {
|
||||||
|
api.cache(false);
|
||||||
|
return {
|
||||||
|
presets: ['@babel/preset-env'],
|
||||||
|
plugins: ['@babel/plugin-transform-runtime']
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -29,7 +29,7 @@ BenchWarmer.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
push: function(name, fn) {
|
push: function(name, fn) {
|
||||||
if (this.names.indexOf(name) == -1) {
|
if (this.names.indexOf(name) === -1) {
|
||||||
this.names.push(name);
|
this.names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ BenchWarmer.prototype = {
|
|||||||
|
|
||||||
var errors = false, prop, bench;
|
var errors = false, prop, bench;
|
||||||
for (prop in self.errors) {
|
for (prop in self.errors) {
|
||||||
if (self.errors.hasOwnProperty(prop)
|
if (Object.prototype.hasOwnProperty.call(self, prop)
|
||||||
&& self.errors[prop].error.message !== 'EWOT') {
|
&& self.errors[prop].error.message !== 'EWOT') {
|
||||||
errors = true;
|
errors = true;
|
||||||
break;
|
break;
|
||||||
@@ -86,9 +86,8 @@ BenchWarmer.prototype = {
|
|||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
print('\n\nErrors:\n');
|
print('\n\nErrors:\n');
|
||||||
for (prop in self.errors) {
|
Object.keys(self.errors).forEach(function(prop) {
|
||||||
if (self.errors.hasOwnProperty(prop)
|
if (self.errors[prop].error.message !== 'EWOT') {
|
||||||
&& self.errors[prop].error.message !== 'EWOT') {
|
|
||||||
bench = self.errors[prop];
|
bench = self.errors[prop];
|
||||||
print('\n' + bench.name + ':\n');
|
print('\n' + bench.name + ':\n');
|
||||||
print(bench.error.message);
|
print(bench.error.message);
|
||||||
@@ -97,7 +96,7 @@ BenchWarmer.prototype = {
|
|||||||
}
|
}
|
||||||
print('\n');
|
print('\n');
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.3.5",
|
"version": "4.5.3",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>4.3.5</version>
|
<version>4.5.3</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.3.5",
|
"version": "4.5.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
|
|||||||
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
|
|||||||
Handlebars.precompile(ast);
|
Handlebars.precompile(ast);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
|
||||||
|
There are two primary APIs that are used to parse an existing template into the AST:
|
||||||
|
|
||||||
|
#### parseWithoutProcessing
|
||||||
|
|
||||||
|
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### parse
|
||||||
|
|
||||||
|
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||||
|
|
||||||
|
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||||
|
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||||
|
|
||||||
|
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parse(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
### Basic
|
### Basic
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as Handlebars from 'handlebars/runtime'
|
import * as Handlebars from 'handlebars/runtime';
|
||||||
import hbs from 'handlebars-inline-precompile';
|
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) {
|
Handlebars.registerHelper('loud', function(text) {
|
||||||
return text.toUpperCase();
|
return text.toUpperCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
const template = hbs`{{loud name}}`;
|
const template = hbs`{{loud name}}`;
|
||||||
const output = template({name: 'yehuda'})
|
const output = template({name: 'yehuda'});
|
||||||
|
|
||||||
assertEquals(output, 'YEHUDA')
|
assertEquals(output, 'YEHUDA');
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ for i in dist/*-test.js ; do
|
|||||||
echo "----------------------"
|
echo "----------------------"
|
||||||
node "$i"
|
node "$i"
|
||||||
echo "Success"
|
echo "Success"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ testFiles
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: entryPoints,
|
entry: entryPoints,
|
||||||
|
mode: 'production',
|
||||||
output: {
|
output: {
|
||||||
filename: '[name]',
|
filename: '[name]',
|
||||||
path: __dirname + '/dist'
|
path: __dirname + '/dist'
|
||||||
|
|||||||
+4
-6
@@ -2,17 +2,16 @@ import runtime from './handlebars.runtime';
|
|||||||
|
|
||||||
// Compiler imports
|
// Compiler imports
|
||||||
import AST from './handlebars/compiler/ast';
|
import AST from './handlebars/compiler/ast';
|
||||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base';
|
||||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||||
import Visitor from './handlebars/compiler/visitor';
|
import Visitor from './handlebars/compiler/visitor';
|
||||||
|
|
||||||
import noConflict from './handlebars/no-conflict';
|
import noConflict from './handlebars/no-conflict';
|
||||||
|
|
||||||
let _create = runtime.create;
|
|
||||||
function create() {
|
function create() {
|
||||||
let hb = _create();
|
let hb = runtime.create();
|
||||||
|
hb.create = create;
|
||||||
hb.compile = function(input, options) {
|
hb.compile = function(input, options) {
|
||||||
return compile(input, options, hb);
|
return compile(input, options, hb);
|
||||||
};
|
};
|
||||||
@@ -25,13 +24,12 @@ function create() {
|
|||||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||||
hb.Parser = Parser;
|
hb.Parser = Parser;
|
||||||
hb.parse = parse;
|
hb.parse = parse;
|
||||||
|
hb.parseWithoutProcessing = parseWithoutProcessing;
|
||||||
|
|
||||||
return hb;
|
return hb;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inst = create();
|
let inst = create();
|
||||||
inst.create = create;
|
|
||||||
|
|
||||||
noConflict(inst);
|
noConflict(inst);
|
||||||
|
|
||||||
inst.Visitor = Visitor;
|
inst.Visitor = Visitor;
|
||||||
|
|||||||
+31
-10
@@ -4,31 +4,52 @@ import * as base from './handlebars/base';
|
|||||||
// (This is done to easily share code between commonjs and browse envs)
|
// (This is done to easily share code between commonjs and browse envs)
|
||||||
import SafeString from './handlebars/safe-string';
|
import SafeString from './handlebars/safe-string';
|
||||||
import Exception from './handlebars/exception';
|
import Exception from './handlebars/exception';
|
||||||
import * as Utils from './handlebars/utils';
|
import {
|
||||||
import * as runtime from './handlebars/runtime';
|
extend,
|
||||||
|
toString,
|
||||||
|
isFunction,
|
||||||
|
isArray,
|
||||||
|
indexOf,
|
||||||
|
escapeExpression,
|
||||||
|
isEmpty,
|
||||||
|
createFrame,
|
||||||
|
blockParams,
|
||||||
|
appendContextPath
|
||||||
|
} from './handlebars/utils';
|
||||||
|
import {checkRevision, template, wrapProgram, resolvePartial, invokePartial, noop} from './handlebars/runtime';
|
||||||
|
|
||||||
import noConflict from './handlebars/no-conflict';
|
import noConflict from './handlebars/no-conflict';
|
||||||
|
|
||||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||||
function create() {
|
export function create() {
|
||||||
let hb = new base.HandlebarsEnvironment();
|
let hb = new base.HandlebarsEnvironment();
|
||||||
|
|
||||||
Utils.extend(hb, base);
|
extend(hb, base);
|
||||||
hb.SafeString = SafeString;
|
hb.SafeString = SafeString;
|
||||||
hb.Exception = Exception;
|
hb.Exception = Exception;
|
||||||
hb.Utils = Utils;
|
hb.Utils = {
|
||||||
hb.escapeExpression = Utils.escapeExpression;
|
extend,
|
||||||
|
toString,
|
||||||
hb.VM = runtime;
|
isFunction,
|
||||||
|
isArray,
|
||||||
|
indexOf,
|
||||||
|
escapeExpression,
|
||||||
|
isEmpty,
|
||||||
|
createFrame,
|
||||||
|
blockParams,
|
||||||
|
appendContextPath
|
||||||
|
};
|
||||||
|
hb.escapeExpression = escapeExpression;
|
||||||
|
hb.create = create;
|
||||||
|
hb.VM = {checkRevision, template, wrapProgram, resolvePartial, invokePartial, noop};
|
||||||
hb.template = function(spec) {
|
hb.template = function(spec) {
|
||||||
return runtime.template(spec, hb);
|
return template(spec, hb);
|
||||||
};
|
};
|
||||||
|
|
||||||
return hb;
|
return hb;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inst = create();
|
let inst = create();
|
||||||
inst.create = create;
|
|
||||||
|
|
||||||
noConflict(inst);
|
noConflict(inst);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers';
|
|||||||
import {registerDefaultDecorators} from './decorators';
|
import {registerDefaultDecorators} from './decorators';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
export const VERSION = '4.3.5';
|
export const VERSION = '4.5.3';
|
||||||
export const COMPILER_REVISION = 8;
|
export const COMPILER_REVISION = 8;
|
||||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export { parser };
|
|||||||
let yy = {};
|
let yy = {};
|
||||||
extend(yy, Helpers);
|
extend(yy, Helpers);
|
||||||
|
|
||||||
export function parse(input, options) {
|
export function parseWithoutProcessing(input, options) {
|
||||||
// Just return if an already-compiled AST was passed in.
|
// Just return if an already-compiled AST was passed in.
|
||||||
if (input.type === 'Program') { return input; }
|
if (input.type === 'Program') { return input; }
|
||||||
|
|
||||||
@@ -19,6 +19,14 @@ export function parse(input, options) {
|
|||||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
let strip = new WhitespaceControl(options);
|
let ast = parser.parse(input);
|
||||||
return strip.accept(parser.parse(input));
|
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(input, options) {
|
||||||
|
let ast = parseWithoutProcessing(input, options);
|
||||||
|
let strip = new WhitespaceControl(options);
|
||||||
|
|
||||||
|
return strip.accept(ast);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
/* global define */
|
|
||||||
import {isArray} from '../utils';
|
import {isArray} from '../utils';
|
||||||
|
|
||||||
let SourceNode;
|
let SourceNode;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* istanbul ignore next */
|
// the 'source-map' map package is ignored in 'umd' and 'amd' builds (via webpack-config)
|
||||||
if (typeof define !== 'function' || !define.amd) {
|
let SourceMap = require('source-map');
|
||||||
// We don't support this in AMD environments. For these environments, we asusme that
|
SourceNode = SourceMap.SourceNode;
|
||||||
// they are running on the browser and thus have no need for the source-map library.
|
|
||||||
let SourceMap = require('source-map');
|
|
||||||
SourceNode = SourceMap.SourceNode;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* NOP */
|
/* NOP */
|
||||||
}
|
}
|
||||||
@@ -125,14 +120,12 @@ CodeGen.prototype = {
|
|||||||
objectLiteral: function(obj) {
|
objectLiteral: function(obj) {
|
||||||
let pairs = [];
|
let pairs = [];
|
||||||
|
|
||||||
for (let key in obj) {
|
Object.keys(obj).forEach(key => {
|
||||||
if (obj.hasOwnProperty(key)) {
|
let value = castChunk(obj[key], this);
|
||||||
let value = castChunk(obj[key], this);
|
if (value !== 'undefined') {
|
||||||
if (value !== 'undefined') {
|
pairs.push([this.quotedString(key), ':', value]);
|
||||||
pairs.push([this.quotedString(key), ':', value]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
let ret = this.generateList(pairs);
|
let ret = this.generateList(pairs);
|
||||||
ret.prepend('{');
|
ret.prepend('{');
|
||||||
|
|||||||
@@ -54,9 +54,7 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
options.blockParams = options.blockParams || [];
|
options.blockParams = options.blockParams || [];
|
||||||
|
|
||||||
// These changes will propagate to the other compiler components
|
options.knownHelpers = extend(Object.create(null), {
|
||||||
let knownHelpers = options.knownHelpers;
|
|
||||||
options.knownHelpers = {
|
|
||||||
'helperMissing': true,
|
'helperMissing': true,
|
||||||
'blockHelperMissing': true,
|
'blockHelperMissing': true,
|
||||||
'each': true,
|
'each': true,
|
||||||
@@ -65,15 +63,7 @@ Compiler.prototype = {
|
|||||||
'with': true,
|
'with': true,
|
||||||
'log': true,
|
'log': true,
|
||||||
'lookup': true
|
'lookup': true
|
||||||
};
|
}, options.knownHelpers);
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.accept(program);
|
return this.accept(program);
|
||||||
},
|
},
|
||||||
@@ -369,7 +359,6 @@ Compiler.prototype = {
|
|||||||
if (isEligible && !isHelper) {
|
if (isEligible && !isHelper) {
|
||||||
let name = sexpr.path.parts[0],
|
let name = sexpr.path.parts[0],
|
||||||
options = this.options;
|
options = this.options;
|
||||||
|
|
||||||
if (options.knownHelpers[name]) {
|
if (options.knownHelpers[name]) {
|
||||||
isHelper = true;
|
isHelper = true;
|
||||||
} else if (options.knownHelpersOnly) {
|
} else if (options.knownHelpersOnly) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
|||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
import {isArray} from '../utils';
|
import {isArray} from '../utils';
|
||||||
import CodeGen from './code-gen';
|
import CodeGen from './code-gen';
|
||||||
|
import {dangerousPropertyRegex} from '../helpers/lookup';
|
||||||
|
|
||||||
function Literal(value) {
|
function Literal(value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -13,9 +14,8 @@ JavaScriptCompiler.prototype = {
|
|||||||
// PUBLIC API: You can override these methods in a subclass to provide
|
// PUBLIC API: You can override these methods in a subclass to provide
|
||||||
// alternative compiled forms for name lookup and buffering semantics
|
// alternative compiled forms for name lookup and buffering semantics
|
||||||
nameLookup: function(parent, name/* , type*/) {
|
nameLookup: function(parent, name/* , type*/) {
|
||||||
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',"constructor")'];
|
if (dangerousPropertyRegex.test(name)) {
|
||||||
|
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',', JSON.stringify(name), ')'];
|
||||||
if (name === 'constructor') {
|
|
||||||
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
|
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
|
||||||
}
|
}
|
||||||
return _actualLookup();
|
return _actualLookup();
|
||||||
@@ -218,13 +218,13 @@ JavaScriptCompiler.prototype = {
|
|||||||
// aliases will not be used, but this case is already being run on the client and
|
// aliases will not be used, but this case is already being run on the client and
|
||||||
// we aren't concern about minimizing the template size.
|
// we aren't concern about minimizing the template size.
|
||||||
let aliasCount = 0;
|
let aliasCount = 0;
|
||||||
for (let alias in this.aliases) { // eslint-disable-line guard-for-in
|
Object.keys(this.aliases).forEach(alias => {
|
||||||
let node = this.aliases[alias];
|
let node = this.aliases[alias];
|
||||||
if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
|
if (node.children && node.referenceCount > 1) {
|
||||||
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
|
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
|
||||||
node.children[0] = 'alias' + aliasCount;
|
node.children[0] = 'alias' + aliasCount;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
||||||
|
|
||||||
@@ -543,7 +543,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
if (this.hash) {
|
if (this.hash) {
|
||||||
this.hashes.push(this.hash);
|
this.hashes.push(this.hash);
|
||||||
}
|
}
|
||||||
this.hash = {values: [], types: [], contexts: [], ids: []};
|
this.hash = {values: {}, types: [], contexts: [], ids: []};
|
||||||
},
|
},
|
||||||
popHash: function() {
|
popHash: function() {
|
||||||
let hash = this.hash;
|
let hash = this.hash;
|
||||||
@@ -1091,6 +1091,7 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
||||||
let options = this.setupParams(helper, paramSize, params);
|
let options = this.setupParams(helper, paramSize, params);
|
||||||
|
options.loc = JSON.stringify(this.source.currentLocation);
|
||||||
options = this.objectLiteral(options);
|
options = this.objectLiteral(options);
|
||||||
if (useRegister) {
|
if (useRegister) {
|
||||||
this.useRegister('options');
|
this.useRegister('options');
|
||||||
@@ -1150,7 +1151,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
|
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )'];
|
||||||
} else {
|
} else {
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
function Exception(message, node) {
|
||||||
let loc = node && node.loc,
|
let loc = node && node.loc,
|
||||||
line,
|
line,
|
||||||
column;
|
endLineNumber,
|
||||||
|
column,
|
||||||
|
endColumn;
|
||||||
|
|
||||||
if (loc) {
|
if (loc) {
|
||||||
line = loc.start.line;
|
line = loc.start.line;
|
||||||
|
endLineNumber = loc.end.line;
|
||||||
column = loc.start.column;
|
column = loc.start.column;
|
||||||
|
endColumn = loc.end.column;
|
||||||
|
|
||||||
message += ' - ' + line + ':' + column;
|
message += ' - ' + line + ':' + column;
|
||||||
}
|
}
|
||||||
@@ -27,6 +32,7 @@ function Exception(message, node) {
|
|||||||
try {
|
try {
|
||||||
if (loc) {
|
if (loc) {
|
||||||
this.lineNumber = line;
|
this.lineNumber = line;
|
||||||
|
this.endLineNumber = endLineNumber;
|
||||||
|
|
||||||
// Work around issue under safari where we can't directly set the column value
|
// Work around issue under safari where we can't directly set the column value
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
@@ -35,8 +41,13 @@ function Exception(message, node) {
|
|||||||
value: column,
|
value: column,
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(this, 'endColumn', {
|
||||||
|
value: endColumn,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.column = column;
|
this.column = column;
|
||||||
|
this.endColumn = endColumn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (nop) {
|
} catch (nop) {
|
||||||
|
|||||||
@@ -49,21 +49,29 @@ export default function(instance) {
|
|||||||
execIteration(i, i, i === context.length - 1);
|
execIteration(i, i, i === context.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (global.Symbol && context[global.Symbol.iterator]) {
|
||||||
|
const newContext = [];
|
||||||
|
const iterator = context[global.Symbol.iterator]();
|
||||||
|
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||||
|
newContext.push(it.value);
|
||||||
|
}
|
||||||
|
context = newContext;
|
||||||
|
for (let j = context.length; i < j; i++) {
|
||||||
|
execIteration(i, i, i === context.length - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let priorKey;
|
let priorKey;
|
||||||
|
|
||||||
for (let key in context) {
|
Object.keys(context).forEach(key => {
|
||||||
if (context.hasOwnProperty(key)) {
|
// We're running the iterations one step out of sync so we can detect
|
||||||
// We're running the iterations one step out of sync so we can detect
|
// the last iteration without have to scan the object twice and create
|
||||||
// the last iteration without have to scan the object twice and create
|
// an itermediate keys array.
|
||||||
// an itermediate keys array.
|
if (priorKey !== undefined) {
|
||||||
if (priorKey !== undefined) {
|
execIteration(priorKey, i - 1);
|
||||||
execIteration(priorKey, i - 1);
|
|
||||||
}
|
|
||||||
priorKey = key;
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
priorKey = key;
|
||||||
|
i++;
|
||||||
|
});
|
||||||
if (priorKey !== undefined) {
|
if (priorKey !== undefined) {
|
||||||
execIteration(priorKey, i - 1, true);
|
execIteration(priorKey, i - 1, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import {isEmpty, isFunction} from '../utils';
|
import { isEmpty, isFunction } from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('if', function(conditional, options) {
|
instance.registerHelper('if', function(conditional, options) {
|
||||||
|
if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');}
|
||||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||||
|
|
||||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||||
@@ -15,6 +17,7 @@ export default function(instance) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
instance.registerHelper('unless', function(conditional, options) {
|
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});
|
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
|
||||||
|
|
||||||
export default function(instance) {
|
export default function(instance) {
|
||||||
instance.registerHelper('lookup', function(obj, field) {
|
instance.registerHelper('lookup', function(obj, field) {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
|
if (dangerousPropertyRegex.test(String(field)) && !Object.prototype.propertyIsEnumerable.call(obj, field)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return obj[field];
|
return obj[field];
|
||||||
|
|||||||
@@ -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) {
|
export default function(instance) {
|
||||||
instance.registerHelper('with', function(context, options) {
|
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 (isFunction(context)) { context = context.call(this); }
|
||||||
|
|
||||||
let fn = options.fn;
|
let fn = options.fn;
|
||||||
|
|||||||
+12
-12
@@ -1,4 +1,4 @@
|
|||||||
import * as Utils from './utils';
|
import {extend, escapeExpression} from './utils';
|
||||||
import Exception from './exception';
|
import Exception from './exception';
|
||||||
import {COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES} from './base';
|
import {COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES} from './base';
|
||||||
import {moveHelperToHooks} from './helpers';
|
import {moveHelperToHooks} from './helpers';
|
||||||
@@ -44,14 +44,14 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
function invokePartialWrapper(partial, context, options) {
|
function invokePartialWrapper(partial, context, options) {
|
||||||
if (options.hash) {
|
if (options.hash) {
|
||||||
context = Utils.extend({}, context, options.hash);
|
context = extend({}, context, options.hash);
|
||||||
if (options.ids) {
|
if (options.ids) {
|
||||||
options.ids[0] = true;
|
options.ids[0] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
|
|
||||||
let optionsWithHooks = Utils.extend({}, options, {hooks: this.hooks});
|
let optionsWithHooks = extend({}, options, {hooks: this.hooks});
|
||||||
|
|
||||||
let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
|
let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
|
||||||
|
|
||||||
@@ -79,9 +79,9 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
// Just add water
|
// Just add water
|
||||||
let container = {
|
let container = {
|
||||||
strict: function(obj, name) {
|
strict: function(obj, name, loc) {
|
||||||
if (!(name in obj)) {
|
if (!obj || !(name in obj)) {
|
||||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
throw new Exception('"' + name + '" not defined in ' + obj, { loc: loc });
|
||||||
}
|
}
|
||||||
return obj[name];
|
return obj[name];
|
||||||
},
|
},
|
||||||
@@ -97,7 +97,7 @@ export function template(templateSpec, env) {
|
|||||||
return typeof current === 'function' ? current.call(context) : current;
|
return typeof current === 'function' ? current.call(context) : current;
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeExpression: Utils.escapeExpression,
|
escapeExpression: escapeExpression,
|
||||||
invokePartial: invokePartialWrapper,
|
invokePartial: invokePartialWrapper,
|
||||||
|
|
||||||
fn: function(i) {
|
fn: function(i) {
|
||||||
@@ -158,13 +158,13 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
container.helpers = Utils.extend({}, env.helpers, options.helpers);
|
container.helpers = extend({}, env.helpers, options.helpers);
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
container.partials = Utils.extend({}, env.partials, options.partials);
|
container.partials = extend({}, env.partials, options.partials);
|
||||||
}
|
}
|
||||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||||
container.decorators = Utils.extend({}, env.decorators, options.decorators);
|
container.decorators = extend({}, env.decorators, options.decorators);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.hooks = {};
|
container.hooks = {};
|
||||||
@@ -258,7 +258,7 @@ export function invokePartial(partial, context, options) {
|
|||||||
return fn(context, options);
|
return fn(context, options);
|
||||||
};
|
};
|
||||||
if (fn.partials) {
|
if (fn.partials) {
|
||||||
options.partials = Utils.extend({}, options.partials, fn.partials);
|
options.partials = extend({}, options.partials, fn.partials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
|||||||
if (fn.decorator) {
|
if (fn.decorator) {
|
||||||
let props = {};
|
let props = {};
|
||||||
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
||||||
Utils.extend(prog, props);
|
extend(prog, props);
|
||||||
}
|
}
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-10
@@ -33,18 +33,23 @@ export let toString = Object.prototype.toString;
|
|||||||
// Sourced from lodash
|
// Sourced from lodash
|
||||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||||
/* eslint-disable func-style */
|
/* eslint-disable func-style */
|
||||||
let isFunction = function(value) {
|
export const isFunction = createIsFunction();
|
||||||
return typeof value === 'function';
|
|
||||||
};
|
|
||||||
// fallback for older versions of Chrome and Safari
|
function createIsFunction() {
|
||||||
/* istanbul ignore next */
|
// istanbul ignore next
|
||||||
if (isFunction(/x/)) {
|
// eslint-disable-next-line no-constant-condition
|
||||||
isFunction = function(value) {
|
if (typeof /x/ === 'function') {
|
||||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
// fallback for older chrome/firefox versions
|
||||||
|
return function isFunction(value) {
|
||||||
|
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return function isFunction(value) {
|
||||||
|
return typeof value === 'function';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export {isFunction};
|
|
||||||
/* eslint-enable func-style */
|
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
export const isArray = Array.isArray || function(value) {
|
export const isArray = Array.isArray || function(value) {
|
||||||
@@ -62,6 +67,7 @@ export function indexOf(array, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function escapeExpression(string) {
|
export function escapeExpression(string) {
|
||||||
if (typeof string !== 'string') {
|
if (typeof string !== 'string') {
|
||||||
// don't escape SafeStrings, since they're already safe
|
// don't escape SafeStrings, since they're already safe
|
||||||
@@ -83,6 +89,7 @@ export function escapeExpression(string) {
|
|||||||
return string.replace(badChars, escapeChar);
|
return string.replace(badChars, escapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function isEmpty(value) {
|
export function isEmpty(value) {
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import Async from 'neo-async';
|
import Async from 'neo-async';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as Handlebars from './handlebars';
|
import Handlebars from './handlebars';
|
||||||
import {basename} from 'path';
|
import {basename} from 'path';
|
||||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||||
|
|
||||||
|
|||||||
Generated
+5760
-1501
File diff suppressed because it is too large
Load Diff
+20
-11
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"barename": "handlebars",
|
"barename": "handlebars",
|
||||||
"version": "4.3.5",
|
"version": "4.5.3",
|
||||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||||
"homepage": "http://www.handlebarsjs.com/",
|
"homepage": "http://www.handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -29,37 +29,46 @@
|
|||||||
"uglify-js": "^3.1.4"
|
"uglify-js": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.7.4",
|
||||||
|
"@babel/plugin-transform-modules-amd": "^7.7.4",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "^7.7.4",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.7.4",
|
||||||
|
"@babel/preset-env": "^7.7.4",
|
||||||
|
"@babel/runtime": "^7.7.4",
|
||||||
|
"@knappi/grunt-saucelabs": "^9.0.2",
|
||||||
"aws-sdk": "^2.1.49",
|
"aws-sdk": "^2.1.49",
|
||||||
"babel-loader": "^5.0.0",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-runtime": "^5.1.10",
|
"babel-plugin-add-module-exports": "^1.0.2",
|
||||||
"benchmark": "~1.0",
|
"benchmark": "~1.0",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"dirty-chai": "^2.0.1",
|
||||||
"dtslint": "^0.5.5",
|
"dtslint": "^0.5.5",
|
||||||
"dustjs-linkedin": "^2.0.2",
|
"dustjs-linkedin": "^2.0.2",
|
||||||
"eco": "~1.1.0-rc-3",
|
"eco": "~1.1.0-rc-3",
|
||||||
"grunt": "^1.0.3",
|
"eslint-plugin-compat": "^3.3.0",
|
||||||
"grunt-babel": "^5.0.0",
|
"eslint-plugin-es5": "^1.4.1",
|
||||||
|
"grunt": "^1.0.4",
|
||||||
|
"grunt-babel": "^8.0.0",
|
||||||
"grunt-bg-shell": "^2.3.3",
|
"grunt-bg-shell": "^2.3.3",
|
||||||
"grunt-cli": "^1",
|
"grunt-cli": "^1",
|
||||||
"grunt-contrib-clean": "^1",
|
"grunt-contrib-clean": "^1",
|
||||||
"grunt-contrib-concat": "^1",
|
"grunt-contrib-concat": "^1",
|
||||||
"grunt-contrib-connect": "^1",
|
"grunt-contrib-connect": "^1",
|
||||||
"grunt-contrib-copy": "^1",
|
"grunt-contrib-copy": "^1",
|
||||||
"grunt-contrib-requirejs": "^1",
|
|
||||||
"grunt-contrib-uglify": "^1",
|
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-eslint": "^20.1.0",
|
"grunt-eslint": "^20.1.0",
|
||||||
"grunt-saucelabs": "github:nknapp/grunt-saucelabs",
|
"grunt-webpack": "^3.1.3",
|
||||||
"grunt-webpack": "^1.0.8",
|
|
||||||
"istanbul": "^0.3.0",
|
"istanbul": "^0.3.0",
|
||||||
"jison": "~0.3.0",
|
"jison": "~0.3.0",
|
||||||
"mocha": "^5",
|
"mocha": "^5",
|
||||||
"mock-stdin": "^0.3.0",
|
"mock-stdin": "^0.3.0",
|
||||||
"mustache": "^2.1.3",
|
"mustache": "^2.1.3",
|
||||||
"semver": "^5.0.1",
|
"semver": "^5.0.1",
|
||||||
|
"sinon": "^7.5.0",
|
||||||
"typescript": "^3.4.3",
|
"typescript": "^3.4.3",
|
||||||
"underscore": "^1.5.1",
|
"underscore": "^1.5.1",
|
||||||
"webpack": "^1.12.6",
|
"webpack": "^4.41.2",
|
||||||
"webpack-dev-server": "^1.12.1"
|
"webpack-dev-server": "^3.9.0"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
|
|||||||
+129
-3
@@ -2,13 +2,139 @@
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.5...master)
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...master)
|
||||||
|
|
||||||
## v4.3.5 - October 2nd, 2019
|
## v4.5.3 - November 18th, 2019
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7
|
||||||
|
- fix: add more properties required to be enumerable - 1988878
|
||||||
|
|
||||||
|
Chores / Build:
|
||||||
|
- fix: use !== 0 instead of != 0 - c02b05f
|
||||||
|
- add chai and dirty-chai and sinon, for cleaner test-assertions and spies,
|
||||||
|
deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0
|
||||||
|
|
||||||
|
Security:
|
||||||
|
|
||||||
|
- The properties `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__`
|
||||||
|
have been added to the list of "properties that must be enumerable".
|
||||||
|
If a property by that name is found and not enumerable on its parent,
|
||||||
|
it will silently evaluate to `undefined`. This is done in both the compiled template and the "lookup"-helper.
|
||||||
|
This will prevent new Remote-Code-Execution exploits that have been
|
||||||
|
published recently.
|
||||||
|
|
||||||
|
Compatibility notes:
|
||||||
|
|
||||||
|
- Due to the security-fixes. The semantics of the templates using
|
||||||
|
`__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` in the respect that those expression now return
|
||||||
|
`undefined` rather than their actual value from the proto.
|
||||||
|
- The semantics have not changed in cases where the properties are enumerable, as in:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
__proto__: 'some string'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The change may be breaking in that respect, but we still only
|
||||||
|
increase the patch-version, because the incompatible use-cases
|
||||||
|
are not intended, undocumented and far less important than fixing
|
||||||
|
Remote-Code-Execution exploits on existing systems.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...v4.5.3)
|
||||||
|
|
||||||
|
## v4.5.2 - November 13th, 2019
|
||||||
|
# Bugfixes
|
||||||
|
|
||||||
|
- fix: use String(field) in lookup when checking for "constructor" - d541378
|
||||||
|
- test: add fluent API for testing Handlebars - c2ac79c
|
||||||
|
|
||||||
|
Compatibility notes:
|
||||||
|
- no incompatibility are to be expected
|
||||||
|
|
||||||
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.1...v4.5.2)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
|
||||||
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.1...v4.4.2)
|
||||||
|
|
||||||
|
## v4.4.1 - October 2nd, 2019
|
||||||
- [#1562](https://github.com/wycats/handlebars.js/issues/1562) - Error message for syntax error missing location in 4.2.1+
|
- [#1562](https://github.com/wycats/handlebars.js/issues/1562) - Error message for syntax error missing location in 4.2.1+
|
||||||
|
|
||||||
|
|
||||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.4...v4.3.5)
|
|
||||||
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.0...v4.4.1)
|
||||||
|
|
||||||
|
## v4.4.0 - September 29th, 2019
|
||||||
|
- Added support for iterable objects in {{#each}} helper (#1557) - cf7545e
|
||||||
|
|
||||||
|
|
||||||
|
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.4...v4.4.0)
|
||||||
|
|
||||||
## v4.3.4 - September 28th, 2019
|
## v4.3.4 - September 28th, 2019
|
||||||
- fix: harden "propertyIsEnumerable"-check - ff4d827
|
- fix: harden "propertyIsEnumerable"-check - ff4d827
|
||||||
|
|||||||
+15
-5
@@ -1,14 +1,20 @@
|
|||||||
{
|
{
|
||||||
|
"extends": [
|
||||||
|
"../.eslintrc.js",
|
||||||
|
"plugin:es5/no-es2015"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"es5"
|
||||||
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
"CompilerContext": true,
|
"CompilerContext": true,
|
||||||
"Handlebars": true,
|
"Handlebars": true,
|
||||||
"handlebarsEnv": true,
|
"handlebarsEnv": true,
|
||||||
|
|
||||||
"shouldCompileTo": true,
|
"shouldCompileTo": true,
|
||||||
"shouldCompileToWithPartials": true,
|
"shouldCompileToWithPartials": true,
|
||||||
"shouldThrow": true,
|
"shouldThrow": true,
|
||||||
|
"expectTemplate": true,
|
||||||
"compileWithPartials": true,
|
"compileWithPartials": true,
|
||||||
|
|
||||||
"console": true,
|
"console": true,
|
||||||
"require": true,
|
"require": true,
|
||||||
"suite": true,
|
"suite": true,
|
||||||
@@ -21,16 +27,20 @@
|
|||||||
"start": true,
|
"start": true,
|
||||||
"stop": true,
|
"stop": true,
|
||||||
"ok": true,
|
"ok": true,
|
||||||
|
"sinon": true,
|
||||||
"strictEqual": true,
|
"strictEqual": true,
|
||||||
"define": true
|
"define": true,
|
||||||
|
"expect": true,
|
||||||
|
"chai": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"mocha": true
|
"mocha": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
// Disabling for tests, for now.
|
// Disabling for tests, for now.
|
||||||
"no-path-concat": 0,
|
"no-path-concat": "off",
|
||||||
|
|
||||||
"no-var": 0
|
"no-var": "off",
|
||||||
|
"dot-notation": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,12 +18,14 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/spec/env/json2.js"></script>
|
|
||||||
<script src="/spec/env/require.js"></script>
|
<script src="/spec/env/require.js"></script>
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
|
|||||||
+4
-3
@@ -19,14 +19,15 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/spec/env/json2.js"></script>
|
|
||||||
<script src="/spec/env/require.js"></script>
|
<script src="/spec/env/require.js"></script>
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var requireFailure;
|
var requireFailure;
|
||||||
|
|||||||
+105
@@ -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('standalone flags', function() {
|
||||||
describe('mustache', function() {
|
describe('mustache', function() {
|
||||||
it('does not mark mustaches as standalone', function() {
|
it('does not mark mustaches as standalone', function() {
|
||||||
@@ -131,6 +165,54 @@ describe('ast', function() {
|
|||||||
equals(!!ast.body[2].value, true);
|
equals(!!ast.body[2].value, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('blocks - parseWithoutProcessing', function() {
|
||||||
|
it('block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
it('initial block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
});
|
||||||
|
it('mustaches with children', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \n');
|
||||||
|
equals(block.program.body[1].path.original, 'foo');
|
||||||
|
equals(block.program.body[2].value, '\n ');
|
||||||
|
});
|
||||||
|
it('nested block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
|
equals(body[0].value, ' \n');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
});
|
||||||
|
it('column 0 block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it('marks block mustaches as standalone', function() {
|
it('marks block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||||
@@ -204,6 +286,18 @@ describe('ast', function() {
|
|||||||
equals(ast.body[2].value, '');
|
equals(ast.body[2].value, '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('partials - parseWithoutProcessing', function() {
|
||||||
|
it('simple partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[1].indent, '');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('partials', function() {
|
describe('partials', function() {
|
||||||
it('marks partial as standalone', function() {
|
it('marks partial as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{> partial }} ');
|
var ast = Handlebars.parse('{{> partial }} ');
|
||||||
@@ -223,6 +317,17 @@ describe('ast', function() {
|
|||||||
equals(ast.body[1].omit, undefined);
|
equals(ast.body[1].omit, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('comments - parseWithoutProcessing', function() {
|
||||||
|
it('simple comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('comments', function() {
|
describe('comments', function() {
|
||||||
it('marks comment as standalone', function() {
|
it('marks comment as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{! comment }} ');
|
var ast = Handlebars.parse('{{! comment }} ');
|
||||||
|
|||||||
@@ -254,6 +254,37 @@ describe('builtin helpers', function() {
|
|||||||
template({});
|
template({});
|
||||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (global.Symbol && global.Symbol.iterator) {
|
||||||
|
it('each on iterable', function() {
|
||||||
|
function Iterator(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
Iterator.prototype.next = function() {
|
||||||
|
var value = this.arr[this.index];
|
||||||
|
var done = this.index === this.arr.length;
|
||||||
|
if (!done) {
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
return { value: value, done: done };
|
||||||
|
};
|
||||||
|
function Iterable(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
}
|
||||||
|
Iterable.prototype[global.Symbol.iterator] = function() {
|
||||||
|
return new Iterator(this.arr);
|
||||||
|
};
|
||||||
|
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
var goodbyes = new Iterable([{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]);
|
||||||
|
var goodbyesEmpty = new Iterable([]);
|
||||||
|
var hash = {goodbyes: goodbyes, world: 'world'};
|
||||||
|
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||||
|
'each with array argument iterates over the contents when not empty');
|
||||||
|
shouldCompileTo(string, {goodbyes: goodbyesEmpty, world: 'world'}, 'cruel world!',
|
||||||
|
'each with array argument ignores the contents when empty');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#log', function() {
|
describe('#log', function() {
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ describe('compiler', function() {
|
|||||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||||
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
equal(err.propertyIsEnumerable('column'), true, 'Checking error column');
|
equal(Object.prototype.propertyIsEnumerable.call(err, 'column'), true, 'Checking error column');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Vendored
+10
-1
@@ -3,13 +3,22 @@ require('./common');
|
|||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
vm = require('vm');
|
vm = require('vm');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = 'no-conflict';
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
var filename = 'dist/handlebars.js';
|
var filename = 'dist/handlebars.js';
|
||||||
if (global.minimizedTest) {
|
if (global.minimizedTest) {
|
||||||
filename = 'dist/handlebars.min.js';
|
filename = 'dist/handlebars.min.js';
|
||||||
}
|
}
|
||||||
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
|
var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
|
||||||
vm.runInThisContext(distHandlebars, filename);
|
vm.runInThisContext(distHandlebars, filename);
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
|||||||
Vendored
+102
-3
@@ -1,4 +1,6 @@
|
|||||||
var global = (function() { return this; }());
|
var global = (function() {
|
||||||
|
return this;
|
||||||
|
}());
|
||||||
|
|
||||||
var AssertError;
|
var AssertError;
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
@@ -16,10 +18,16 @@ if (Error.captureStackTrace) {
|
|||||||
AssertError = Error;
|
AssertError = Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
||||||
var result = compileWithPartials(string, hashOrArray, partials);
|
var result = compileWithPartials(string, hashOrArray, partials);
|
||||||
if (result !== expected) {
|
if (result !== expected) {
|
||||||
@@ -27,6 +35,9 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||||
var template,
|
var template,
|
||||||
ary,
|
ary,
|
||||||
@@ -36,8 +47,8 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
|||||||
delete hashOrArray.hash;
|
delete hashOrArray.hash;
|
||||||
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||||
ary = [];
|
ary = [];
|
||||||
ary.push(hashOrArray[0]);
|
ary.push(hashOrArray[0]); // input
|
||||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]});
|
||||||
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
|
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
|
||||||
if (hashOrArray[4] != null) {
|
if (hashOrArray[4] != null) {
|
||||||
options.data = !!hashOrArray[4];
|
options.data = !!hashOrArray[4];
|
||||||
@@ -52,12 +63,20 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/
|
||||||
|
*/
|
||||||
global.equals = global.equal = function equals(a, b, msg) {
|
global.equals = global.equal = function equals(a, b, msg) {
|
||||||
if (a !== b) {
|
if (a !== b) {
|
||||||
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
|
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/#method_throw
|
||||||
|
*/
|
||||||
global.shouldThrow = function(callback, type, msg) {
|
global.shouldThrow = function(callback, type, msg) {
|
||||||
var failed;
|
var failed;
|
||||||
try {
|
try {
|
||||||
@@ -75,3 +94,83 @@ global.shouldThrow = function(callback, type, msg) {
|
|||||||
throw new AssertError('It failed to throw', shouldThrow);
|
throw new AssertError('It failed to throw', shouldThrow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
global.expectTemplate = function(templateAsString) {
|
||||||
|
return new HandlebarsTestBench(templateAsString);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function HandlebarsTestBench(templateAsString) {
|
||||||
|
this.templateAsString = templateAsString;
|
||||||
|
this.helpers = {};
|
||||||
|
this.partials = {};
|
||||||
|
this.input = {};
|
||||||
|
this.message = 'Template' + templateAsString + ' does not evaluate to expected output';
|
||||||
|
this.compileOptions = {};
|
||||||
|
this.runtimeOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withInput = function(input) {
|
||||||
|
this.input = input;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
|
||||||
|
this.helpers[name] = helperFunction;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
|
||||||
|
this.partials[name] = partialAsString;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
|
||||||
|
this.compileOptions = compileOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
|
||||||
|
this.runtimeOptions = runtimeOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withMessage = function(message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
|
||||||
|
expect(this._compileAndExeute()).to.equal(expectedOutputAsString);
|
||||||
|
};
|
||||||
|
|
||||||
|
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||||
|
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg) {
|
||||||
|
var self = this;
|
||||||
|
expect(function() {
|
||||||
|
self._compileAndExeute();
|
||||||
|
}).to.throw(errorLike, errMsgMatcher, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._compileAndExeute = function() {
|
||||||
|
var compile = Object.keys(this.partials).length > 0
|
||||||
|
? CompilerContext.compileWithPartial
|
||||||
|
: CompilerContext.compile;
|
||||||
|
|
||||||
|
var combinedRuntimeOptions = this._combineRuntimeOptions();
|
||||||
|
|
||||||
|
var template = compile(this.templateAsString, this.compileOptions);
|
||||||
|
return template(this.input, combinedRuntimeOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
|
||||||
|
var self = this;
|
||||||
|
var combinedRuntimeOptions = {};
|
||||||
|
Object.keys(this.runtimeOptions).forEach(function(key) {
|
||||||
|
combinedRuntimeOptions[key] = self.runtimeOptions[key];
|
||||||
|
});
|
||||||
|
combinedRuntimeOptions.helpers = this.helpers;
|
||||||
|
combinedRuntimeOptions.partials = this.partials;
|
||||||
|
return combinedRuntimeOptions;
|
||||||
|
};
|
||||||
|
|||||||
Vendored
-489
@@ -1,489 +0,0 @@
|
|||||||
/*
|
|
||||||
json2.js
|
|
||||||
2014-02-04
|
|
||||||
|
|
||||||
Public Domain.
|
|
||||||
|
|
||||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
||||||
|
|
||||||
See http://www.JSON.org/js.html
|
|
||||||
|
|
||||||
|
|
||||||
This code should be minified before deployment.
|
|
||||||
See http://javascript.crockford.com/jsmin.html
|
|
||||||
|
|
||||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
|
||||||
NOT CONTROL.
|
|
||||||
|
|
||||||
|
|
||||||
This file creates a global JSON object containing two methods: stringify
|
|
||||||
and parse.
|
|
||||||
|
|
||||||
JSON.stringify(value, replacer, space)
|
|
||||||
value any JavaScript value, usually an object or array.
|
|
||||||
|
|
||||||
replacer an optional parameter that determines how object
|
|
||||||
values are stringified for objects. It can be a
|
|
||||||
function or an array of strings.
|
|
||||||
|
|
||||||
space an optional parameter that specifies the indentation
|
|
||||||
of nested structures. If it is omitted, the text will
|
|
||||||
be packed without extra whitespace. If it is a number,
|
|
||||||
it will specify the number of spaces to indent at each
|
|
||||||
level. If it is a string (such as '\t' or ' '),
|
|
||||||
it contains the characters used to indent at each level.
|
|
||||||
|
|
||||||
This method produces a JSON text from a JavaScript value.
|
|
||||||
|
|
||||||
When an object value is found, if the object contains a toJSON
|
|
||||||
method, its toJSON method will be called and the result will be
|
|
||||||
stringified. A toJSON method does not serialize: it returns the
|
|
||||||
value represented by the name/value pair that should be serialized,
|
|
||||||
or undefined if nothing should be serialized. The toJSON method
|
|
||||||
will be passed the key associated with the value, and this will be
|
|
||||||
bound to the value
|
|
||||||
|
|
||||||
For example, this would serialize Dates as ISO strings.
|
|
||||||
|
|
||||||
Date.prototype.toJSON = function (key) {
|
|
||||||
function f(n) {
|
|
||||||
// Format integers to have at least two digits.
|
|
||||||
return n < 10 ? '0' + n : n;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getUTCFullYear() + '-' +
|
|
||||||
f(this.getUTCMonth() + 1) + '-' +
|
|
||||||
f(this.getUTCDate()) + 'T' +
|
|
||||||
f(this.getUTCHours()) + ':' +
|
|
||||||
f(this.getUTCMinutes()) + ':' +
|
|
||||||
f(this.getUTCSeconds()) + 'Z';
|
|
||||||
};
|
|
||||||
|
|
||||||
You can provide an optional replacer method. It will be passed the
|
|
||||||
key and value of each member, with this bound to the containing
|
|
||||||
object. The value that is returned from your method will be
|
|
||||||
serialized. If your method returns undefined, then the member will
|
|
||||||
be excluded from the serialization.
|
|
||||||
|
|
||||||
If the replacer parameter is an array of strings, then it will be
|
|
||||||
used to select the members to be serialized. It filters the results
|
|
||||||
such that only members with keys listed in the replacer array are
|
|
||||||
stringified.
|
|
||||||
|
|
||||||
Values that do not have JSON representations, such as undefined or
|
|
||||||
functions, will not be serialized. Such values in objects will be
|
|
||||||
dropped; in arrays they will be replaced with null. You can use
|
|
||||||
a replacer function to replace those with JSON values.
|
|
||||||
JSON.stringify(undefined) returns undefined.
|
|
||||||
|
|
||||||
The optional space parameter produces a stringification of the
|
|
||||||
value that is filled with line breaks and indentation to make it
|
|
||||||
easier to read.
|
|
||||||
|
|
||||||
If the space parameter is a non-empty string, then that string will
|
|
||||||
be used for indentation. If the space parameter is a number, then
|
|
||||||
the indentation will be that many spaces.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
|
||||||
// text is '["e",{"pluribus":"unum"}]'
|
|
||||||
|
|
||||||
|
|
||||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
|
||||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
|
||||||
|
|
||||||
text = JSON.stringify([new Date()], function (key, value) {
|
|
||||||
return this[key] instanceof Date ?
|
|
||||||
'Date(' + this[key] + ')' : value;
|
|
||||||
});
|
|
||||||
// text is '["Date(---current time---)"]'
|
|
||||||
|
|
||||||
|
|
||||||
JSON.parse(text, reviver)
|
|
||||||
This method parses a JSON text to produce an object or array.
|
|
||||||
It can throw a SyntaxError exception.
|
|
||||||
|
|
||||||
The optional reviver parameter is a function that can filter and
|
|
||||||
transform the results. It receives each of the keys and values,
|
|
||||||
and its return value is used instead of the original value.
|
|
||||||
If it returns what it received, then the structure is not modified.
|
|
||||||
If it returns undefined then the member is deleted.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
// Parse the text. Values that look like ISO date strings will
|
|
||||||
// be converted to Date objects.
|
|
||||||
|
|
||||||
myData = JSON.parse(text, function (key, value) {
|
|
||||||
var a;
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
a =
|
|
||||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
|
||||||
if (a) {
|
|
||||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
|
||||||
+a[5], +a[6]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
|
||||||
var d;
|
|
||||||
if (typeof value === 'string' &&
|
|
||||||
value.slice(0, 5) === 'Date(' &&
|
|
||||||
value.slice(-1) === ')') {
|
|
||||||
d = new Date(value.slice(5, -1));
|
|
||||||
if (d) {
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
This is a reference implementation. You are free to copy, modify, or
|
|
||||||
redistribute.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*jslint evil: true, regexp: true */
|
|
||||||
|
|
||||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
|
||||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
|
||||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
|
||||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
|
||||||
test, toJSON, toString, valueOf
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// Create a JSON object only if one does not already exist. We create the
|
|
||||||
// methods in a closure to avoid creating global variables.
|
|
||||||
|
|
||||||
if (typeof JSON !== 'object') {
|
|
||||||
JSON = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function f(n) {
|
|
||||||
// Format integers to have at least two digits.
|
|
||||||
return n < 10 ? '0' + n : n;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Date.prototype.toJSON !== 'function') {
|
|
||||||
|
|
||||||
Date.prototype.toJSON = function () {
|
|
||||||
|
|
||||||
return isFinite(this.valueOf())
|
|
||||||
? this.getUTCFullYear() + '-' +
|
|
||||||
f(this.getUTCMonth() + 1) + '-' +
|
|
||||||
f(this.getUTCDate()) + 'T' +
|
|
||||||
f(this.getUTCHours()) + ':' +
|
|
||||||
f(this.getUTCMinutes()) + ':' +
|
|
||||||
f(this.getUTCSeconds()) + 'Z'
|
|
||||||
: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
String.prototype.toJSON =
|
|
||||||
Number.prototype.toJSON =
|
|
||||||
Boolean.prototype.toJSON = function () {
|
|
||||||
return this.valueOf();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var cx,
|
|
||||||
escapable,
|
|
||||||
gap,
|
|
||||||
indent,
|
|
||||||
meta,
|
|
||||||
rep;
|
|
||||||
|
|
||||||
|
|
||||||
function quote(string) {
|
|
||||||
|
|
||||||
// If the string contains no control characters, no quote characters, and no
|
|
||||||
// backslash characters, then we can safely slap some quotes around it.
|
|
||||||
// Otherwise we must also replace the offending characters with safe escape
|
|
||||||
// sequences.
|
|
||||||
|
|
||||||
escapable.lastIndex = 0;
|
|
||||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
|
||||||
var c = meta[a];
|
|
||||||
return typeof c === 'string'
|
|
||||||
? c
|
|
||||||
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
||||||
}) + '"' : '"' + string + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function str(key, holder) {
|
|
||||||
|
|
||||||
// Produce a string from holder[key].
|
|
||||||
|
|
||||||
var i, // The loop counter.
|
|
||||||
k, // The member key.
|
|
||||||
v, // The member value.
|
|
||||||
length,
|
|
||||||
mind = gap,
|
|
||||||
partial,
|
|
||||||
value = holder[key];
|
|
||||||
|
|
||||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
|
||||||
|
|
||||||
if (value && typeof value === 'object' &&
|
|
||||||
typeof value.toJSON === 'function') {
|
|
||||||
value = value.toJSON(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were called with a replacer function, then call the replacer to
|
|
||||||
// obtain a replacement value.
|
|
||||||
|
|
||||||
if (typeof rep === 'function') {
|
|
||||||
value = rep.call(holder, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// What happens next depends on the value's type.
|
|
||||||
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'string':
|
|
||||||
return quote(value);
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
|
|
||||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
|
||||||
|
|
||||||
return isFinite(value) ? String(value) : 'null';
|
|
||||||
|
|
||||||
case 'boolean':
|
|
||||||
case 'null':
|
|
||||||
|
|
||||||
// If the value is a boolean or null, convert it to a string. Note:
|
|
||||||
// typeof null does not produce 'null'. The case is included here in
|
|
||||||
// the remote chance that this gets fixed someday.
|
|
||||||
|
|
||||||
return String(value);
|
|
||||||
|
|
||||||
// If the type is 'object', we might be dealing with an object or an array or
|
|
||||||
// null.
|
|
||||||
|
|
||||||
case 'object':
|
|
||||||
|
|
||||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
|
||||||
// so watch out for that case.
|
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make an array to hold the partial results of stringifying this object value.
|
|
||||||
|
|
||||||
gap += indent;
|
|
||||||
partial = [];
|
|
||||||
|
|
||||||
// Is the value an array?
|
|
||||||
|
|
||||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
|
||||||
|
|
||||||
// The value is an array. Stringify every element. Use null as a placeholder
|
|
||||||
// for non-JSON values.
|
|
||||||
|
|
||||||
length = value.length;
|
|
||||||
for (i = 0; i < length; i += 1) {
|
|
||||||
partial[i] = str(i, value) || 'null';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join all of the elements together, separated with commas, and wrap them in
|
|
||||||
// brackets.
|
|
||||||
|
|
||||||
v = partial.length === 0
|
|
||||||
? '[]'
|
|
||||||
: gap
|
|
||||||
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
|
|
||||||
: '[' + partial.join(',') + ']';
|
|
||||||
gap = mind;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the replacer is an array, use it to select the members to be stringified.
|
|
||||||
|
|
||||||
if (rep && typeof rep === 'object') {
|
|
||||||
length = rep.length;
|
|
||||||
for (i = 0; i < length; i += 1) {
|
|
||||||
if (typeof rep[i] === 'string') {
|
|
||||||
k = rep[i];
|
|
||||||
v = str(k, value);
|
|
||||||
if (v) {
|
|
||||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Otherwise, iterate through all of the keys in the object.
|
|
||||||
|
|
||||||
for (k in value) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
|
||||||
v = str(k, value);
|
|
||||||
if (v) {
|
|
||||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join all of the member texts together, separated with commas,
|
|
||||||
// and wrap them in braces.
|
|
||||||
|
|
||||||
v = partial.length === 0
|
|
||||||
? '{}'
|
|
||||||
: gap
|
|
||||||
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
|
|
||||||
: '{' + partial.join(',') + '}';
|
|
||||||
gap = mind;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the JSON object does not yet have a stringify method, give it one.
|
|
||||||
|
|
||||||
if (typeof JSON.stringify !== 'function') {
|
|
||||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
|
||||||
meta = { // table of character substitutions
|
|
||||||
'\b': '\\b',
|
|
||||||
'\t': '\\t',
|
|
||||||
'\n': '\\n',
|
|
||||||
'\f': '\\f',
|
|
||||||
'\r': '\\r',
|
|
||||||
'"' : '\\"',
|
|
||||||
'\\': '\\\\'
|
|
||||||
};
|
|
||||||
JSON.stringify = function (value, replacer, space) {
|
|
||||||
|
|
||||||
// The stringify method takes a value and an optional replacer, and an optional
|
|
||||||
// space parameter, and returns a JSON text. The replacer can be a function
|
|
||||||
// that can replace values, or an array of strings that will select the keys.
|
|
||||||
// A default replacer method can be provided. Use of the space parameter can
|
|
||||||
// produce text that is more easily readable.
|
|
||||||
|
|
||||||
var i;
|
|
||||||
gap = '';
|
|
||||||
indent = '';
|
|
||||||
|
|
||||||
// If the space parameter is a number, make an indent string containing that
|
|
||||||
// many spaces.
|
|
||||||
|
|
||||||
if (typeof space === 'number') {
|
|
||||||
for (i = 0; i < space; i += 1) {
|
|
||||||
indent += ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the space parameter is a string, it will be used as the indent string.
|
|
||||||
|
|
||||||
} else if (typeof space === 'string') {
|
|
||||||
indent = space;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a replacer, it must be a function or an array.
|
|
||||||
// Otherwise, throw an error.
|
|
||||||
|
|
||||||
rep = replacer;
|
|
||||||
if (replacer && typeof replacer !== 'function' &&
|
|
||||||
(typeof replacer !== 'object' ||
|
|
||||||
typeof replacer.length !== 'number')) {
|
|
||||||
throw new Error('JSON.stringify');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a fake root object containing our value under the key of ''.
|
|
||||||
// Return the result of stringifying the value.
|
|
||||||
|
|
||||||
return str('', {'': value});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If the JSON object does not yet have a parse method, give it one.
|
|
||||||
|
|
||||||
if (typeof JSON.parse !== 'function') {
|
|
||||||
cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
|
||||||
JSON.parse = function (text, reviver) {
|
|
||||||
|
|
||||||
// The parse method takes a text and an optional reviver function, and returns
|
|
||||||
// a JavaScript value if the text is a valid JSON text.
|
|
||||||
|
|
||||||
var j;
|
|
||||||
|
|
||||||
function walk(holder, key) {
|
|
||||||
|
|
||||||
// The walk method is used to recursively walk the resulting structure so
|
|
||||||
// that modifications can be made.
|
|
||||||
|
|
||||||
var k, v, value = holder[key];
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
for (k in value) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
|
||||||
v = walk(value, k);
|
|
||||||
if (v !== undefined) {
|
|
||||||
value[k] = v;
|
|
||||||
} else {
|
|
||||||
delete value[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reviver.call(holder, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parsing happens in four stages. In the first stage, we replace certain
|
|
||||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
|
||||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
|
||||||
|
|
||||||
text = String(text);
|
|
||||||
cx.lastIndex = 0;
|
|
||||||
if (cx.test(text)) {
|
|
||||||
text = text.replace(cx, function (a) {
|
|
||||||
return '\\u' +
|
|
||||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the second stage, we run the text against regular expressions that look
|
|
||||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
|
||||||
// because they can cause invocation, and '=' because it can cause mutation.
|
|
||||||
// But just to be safe, we want to reject all unexpected forms.
|
|
||||||
|
|
||||||
// We split the second stage into 4 regexp operations in order to work around
|
|
||||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
|
||||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
|
||||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
|
||||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
|
||||||
// we look to see that the remaining characters are only whitespace or ']' or
|
|
||||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
|
||||||
|
|
||||||
if (/^[\],:{}\s]*$/
|
|
||||||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
|
||||||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
|
||||||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
|
||||||
|
|
||||||
// In the third stage we use the eval function to compile the text into a
|
|
||||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
|
||||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
|
||||||
// in parens to eliminate the ambiguity.
|
|
||||||
|
|
||||||
j = eval('(' + text + ')');
|
|
||||||
|
|
||||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
|
||||||
// each name/value pair to a reviver function for possible transformation.
|
|
||||||
|
|
||||||
return typeof reviver === 'function'
|
|
||||||
? walk({'': j}, '')
|
|
||||||
: j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
|
||||||
|
|
||||||
throw new SyntaxError('JSON.parse');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
Vendored
+9
@@ -1,5 +1,14 @@
|
|||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = require('../../lib');
|
global.Handlebars = require('../../lib');
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
|||||||
Vendored
+5
-1
@@ -39,7 +39,11 @@ if (global.minimizedTest) {
|
|||||||
|
|
||||||
|
|
||||||
function run(env, callback) {
|
function run(env, callback) {
|
||||||
|
if (errors > 0) {
|
||||||
|
throw new Error('Errors have been detected before');
|
||||||
|
}
|
||||||
var mocha = new Mocha();
|
var mocha = new Mocha();
|
||||||
|
mocha.bail();
|
||||||
mocha.ui('bdd');
|
mocha.ui('bdd');
|
||||||
mocha.files = files.slice();
|
mocha.files = files.slice();
|
||||||
if (grep) {
|
if (grep) {
|
||||||
@@ -54,6 +58,6 @@ function run(env, callback) {
|
|||||||
require(env);
|
require(env);
|
||||||
mocha.run(function(errorCount) {
|
mocha.run(function(errorCount) {
|
||||||
errors += errorCount;
|
errors += errorCount;
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+9
@@ -3,6 +3,15 @@ require('./common');
|
|||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
vm = require('vm');
|
vm = require('vm');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = 'no-conflict';
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
var filename = 'dist/handlebars.runtime.js';
|
var filename = 'dist/handlebars.runtime.js';
|
||||||
|
|||||||
+102
-7
@@ -28,14 +28,43 @@ describe('helpers', function() {
|
|||||||
'raw block helper gets raw content');
|
'raw block helper gets raw content');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('helper for nested raw block gets raw content', function() {
|
describe('raw block parsing (with identity helper-function)', function() {
|
||||||
var string = '{{{{a}}}} {{{{b}}}} {{{{/b}}}} {{{{/a}}}}';
|
|
||||||
var helpers = {
|
function runWithIdentityHelper(template, expected) {
|
||||||
a: function(options) {
|
var helpers = {
|
||||||
|
identity: function(options) {
|
||||||
return options.fn();
|
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() {
|
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');
|
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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-2
@@ -19,13 +19,15 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
<script src="/dist/handlebars.js"></script>
|
<script src="/dist/handlebars.js"></script>
|
||||||
|
|
||||||
<script src="/spec/env/json2.js"></script>
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var CompilerContext = {
|
var CompilerContext = {
|
||||||
|
|||||||
+13
-2
@@ -211,12 +211,12 @@ describe('Regressions', function() {
|
|||||||
// It's valid to execute a block against an undefined context, but
|
// It's valid to execute a block against an undefined context, but
|
||||||
// helpers can not do so, so we expect to have an empty object here;
|
// helpers can not do so, so we expect to have an empty object here;
|
||||||
for (var name in this) {
|
for (var name in this) {
|
||||||
if (this.hasOwnProperty(name)) {
|
if (Object.prototype.hasOwnProperty.call(this, name)) {
|
||||||
return 'found';
|
return 'found';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// And to make IE happy, check for the known string as length is not enumerated.
|
// And to make IE happy, check for the known string as length is not enumerated.
|
||||||
return (this == 'bat' ? 'found' : 'not');
|
return (this === 'bat' ? 'found' : 'not');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,4 +323,15 @@ describe('Regressions', function() {
|
|||||||
}, 'useData': true});
|
}, '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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+49
-9
@@ -1,11 +1,26 @@
|
|||||||
describe('security issues', function() {
|
describe('security issues', function() {
|
||||||
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
|
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
|
||||||
it('should not allow constructors to be accessed', function() {
|
it('should not allow constructors to be accessed', function() {
|
||||||
shouldCompileTo('{{constructor.name}}', {}, '');
|
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
|
||||||
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {}, '');
|
.withInput({})
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{constructor.name}}')
|
||||||
|
.withInput({})
|
||||||
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
|
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
|
||||||
|
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
|
||||||
|
.withInput({})
|
||||||
|
.withHelper('list', function(element) {
|
||||||
|
return [element];
|
||||||
|
})
|
||||||
|
.toCompileTo('');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
|
||||||
shouldCompileTo('{{constructor.name}}', {'constructor': {
|
shouldCompileTo('{{constructor.name}}', {'constructor': {
|
||||||
'name': 'here we go'
|
'name': 'here we go'
|
||||||
}}, 'here we go');
|
}}, 'here we go');
|
||||||
@@ -14,6 +29,13 @@ describe('security issues', function() {
|
|||||||
}}, 'here we go');
|
}}, 'here we go');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
|
||||||
|
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
|
||||||
|
'name': 'here we go'
|
||||||
|
}}, 'here we go');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should allow prototype properties that are not constructors', function() {
|
it('should allow prototype properties that are not constructors', function() {
|
||||||
function TestClass() {
|
function TestClass() {
|
||||||
}
|
}
|
||||||
@@ -53,11 +75,11 @@ describe('security issues', function() {
|
|||||||
});
|
});
|
||||||
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
||||||
var functionCalls = [];
|
var functionCalls = [];
|
||||||
shouldThrow(function() {
|
expect(function() {
|
||||||
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
||||||
template({ fn: function() { functionCalls.push('called'); }});
|
template({ fn: function() { functionCalls.push('called'); }});
|
||||||
}, Error);
|
}).to.throw(Error);
|
||||||
equals(functionCalls.length, 0);
|
expect(functionCalls.length).to.equal(0);
|
||||||
});
|
});
|
||||||
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(function() {
|
||||||
@@ -92,13 +114,31 @@ describe('security issues', function() {
|
|||||||
describe('GH-1563', function() {
|
describe('GH-1563', function() {
|
||||||
it('should not allow to access constructor after overriding via __defineGetter__', function() {
|
it('should not allow to access constructor after overriding via __defineGetter__', function() {
|
||||||
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
|
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
|
||||||
return; // Browser does not support this exploit anyway
|
return this.skip(); // Browser does not support this exploit anyway
|
||||||
}
|
}
|
||||||
shouldCompileTo('{{__defineGetter__ "undefined" valueOf }}' +
|
expectTemplate('{{__defineGetter__ "undefined" valueOf }}' +
|
||||||
'{{#with __lookupGetter__ }}' +
|
'{{#with __lookupGetter__ }}' +
|
||||||
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
|
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
|
||||||
'{{constructor.name}}' +
|
'{{constructor.name}}' +
|
||||||
'{{/with}}', {}, '');
|
'{{/with}}')
|
||||||
|
.withInput({})
|
||||||
|
.toThrow(/Missing helper: "__defineGetter__"/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GH-1595', function() {
|
||||||
|
it('properties, that are required to be enumerable', function() {
|
||||||
|
expectTemplate('{{constructor}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{__proto__}}').withInput({}).toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo('');
|
||||||
|
expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo('');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,6 +95,25 @@ describe('strict', function() {
|
|||||||
};
|
};
|
||||||
equals(template({}, {helpers: helpers}), 'success');
|
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() {
|
describe('assume objects', function() {
|
||||||
|
|||||||
@@ -441,4 +441,9 @@ describe('Tokenizer', function() {
|
|||||||
result = tokenize('{{else foo as |bar baz|}}');
|
result = tokenize('{{else foo as |bar baz|}}');
|
||||||
shouldMatchTokens(result, ['OPEN_INVERSE_CHAIN', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
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']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,14 +18,15 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/spec/env/json2.js"></script>
|
|
||||||
<script src="/spec/env/require.js"></script>
|
<script src="/spec/env/require.js"></script>
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
requirejs.config({
|
requirejs.config({
|
||||||
|
|||||||
+5
-3
@@ -18,14 +18,16 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/spec/env/json2.js"></script>
|
|
||||||
<script src="/spec/env/require.js"></script>
|
<script src="/spec/env/require.js"></script>
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
requirejs.config({
|
requirejs.config({
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
|||||||
return 'END_RAW_BLOCK';
|
return 'END_RAW_BLOCK';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<raw>[^\x00]*?/("{{{{") { return 'CONTENT'; }
|
<raw>[^\x00]+?/("{{{{") { return 'CONTENT'; }
|
||||||
|
|
||||||
<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
|
<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
|
||||||
this.popState();
|
this.popState();
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ content
|
|||||||
};
|
};
|
||||||
|
|
||||||
rawBlock
|
rawBlock
|
||||||
: openRawBlock content+ END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
|
: openRawBlock content* END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
|
||||||
;
|
;
|
||||||
|
|
||||||
openRawBlock
|
openRawBlock
|
||||||
|
|||||||
+2
-2
@@ -33,7 +33,7 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
|
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
|
||||||
runner.on('close', function(code) {
|
runner.on('close', function(code) {
|
||||||
if (code != 0) {
|
if (code !== 0) {
|
||||||
grunt.fatal(code + ' tests failed');
|
grunt.fatal(code + ' tests failed');
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
@@ -55,7 +55,7 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
var runner = childProcess.fork('./spec/env/runner', ['--min'], {stdio: 'inherit'});
|
var runner = childProcess.fork('./spec/env/runner', ['--min'], {stdio: 'inherit'});
|
||||||
runner.on('close', function(code) {
|
runner.on('close', function(code) {
|
||||||
if (code != 0) {
|
if (code !== 0) {
|
||||||
grunt.fatal(code + ' tests failed');
|
grunt.fatal(code + ' tests failed');
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
|
|||||||
Vendored
+36
-5
@@ -47,8 +47,8 @@ declare namespace Handlebars {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ParseOptions {
|
export interface ParseOptions {
|
||||||
srcName?: string,
|
srcName?: string;
|
||||||
ignoreStandalone?: boolean
|
ignoreStandalone?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerHelper(name: string, fn: HelperDelegate): void;
|
export function registerHelper(name: string, fn: HelperDelegate): void;
|
||||||
@@ -66,9 +66,9 @@ declare namespace Handlebars {
|
|||||||
export function K(): void;
|
export function K(): void;
|
||||||
export function createFrame(object: any): any;
|
export function createFrame(object: any): any;
|
||||||
export function blockParams(obj: any[], ids: 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 log(level: number, obj: any): void;
|
||||||
export function parse(input: string, options?: ParseOptions): hbs.AST.Program;
|
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 compile<T = any>(input: any, options?: CompileOptions): HandlebarsTemplateDelegate<T>;
|
||||||
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
|
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
|
||||||
export function template<T = any>(precompilation: TemplateSpecification): HandlebarsTemplateDelegate<T>;
|
export function template<T = any>(precompilation: TemplateSpecification): HandlebarsTemplateDelegate<T>;
|
||||||
@@ -86,6 +86,20 @@ declare namespace Handlebars {
|
|||||||
|
|
||||||
export function noConflict(): typeof 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 {
|
export class SafeString {
|
||||||
constructor(str: string);
|
constructor(str: string);
|
||||||
toString(): string;
|
toString(): string;
|
||||||
@@ -270,6 +284,7 @@ declare namespace hbs {
|
|||||||
interface Statement extends Node {}
|
interface Statement extends Node {}
|
||||||
|
|
||||||
interface MustacheStatement extends Statement {
|
interface MustacheStatement extends Statement {
|
||||||
|
type: 'MustacheStatement';
|
||||||
path: PathExpression | Literal;
|
path: PathExpression | Literal;
|
||||||
params: Expression[];
|
params: Expression[];
|
||||||
hash: Hash;
|
hash: Hash;
|
||||||
@@ -280,6 +295,7 @@ declare namespace hbs {
|
|||||||
interface Decorator extends MustacheStatement { }
|
interface Decorator extends MustacheStatement { }
|
||||||
|
|
||||||
interface BlockStatement extends Statement {
|
interface BlockStatement extends Statement {
|
||||||
|
type: 'BlockStatement';
|
||||||
path: PathExpression;
|
path: PathExpression;
|
||||||
params: Expression[];
|
params: Expression[];
|
||||||
hash: Hash;
|
hash: Hash;
|
||||||
@@ -293,6 +309,7 @@ declare namespace hbs {
|
|||||||
interface DecoratorBlock extends BlockStatement { }
|
interface DecoratorBlock extends BlockStatement { }
|
||||||
|
|
||||||
interface PartialStatement extends Statement {
|
interface PartialStatement extends Statement {
|
||||||
|
type: 'PartialStatement';
|
||||||
name: PathExpression | SubExpression;
|
name: PathExpression | SubExpression;
|
||||||
params: Expression[];
|
params: Expression[];
|
||||||
hash: Hash;
|
hash: Hash;
|
||||||
@@ -301,6 +318,7 @@ declare namespace hbs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PartialBlockStatement extends Statement {
|
interface PartialBlockStatement extends Statement {
|
||||||
|
type: 'PartialBlockStatement';
|
||||||
name: PathExpression | SubExpression;
|
name: PathExpression | SubExpression;
|
||||||
params: Expression[];
|
params: Expression[];
|
||||||
hash: Hash;
|
hash: Hash;
|
||||||
@@ -310,11 +328,13 @@ declare namespace hbs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ContentStatement extends Statement {
|
interface ContentStatement extends Statement {
|
||||||
|
type: 'ContentStatement';
|
||||||
value: string;
|
value: string;
|
||||||
original: StripFlags;
|
original: StripFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentStatement extends Statement {
|
interface CommentStatement extends Statement {
|
||||||
|
type: 'CommentStatement';
|
||||||
value: string;
|
value: string;
|
||||||
strip: StripFlags;
|
strip: StripFlags;
|
||||||
}
|
}
|
||||||
@@ -322,12 +342,14 @@ declare namespace hbs {
|
|||||||
interface Expression extends Node {}
|
interface Expression extends Node {}
|
||||||
|
|
||||||
interface SubExpression extends Expression {
|
interface SubExpression extends Expression {
|
||||||
|
type: 'SubExpression';
|
||||||
path: PathExpression;
|
path: PathExpression;
|
||||||
params: Expression[];
|
params: Expression[];
|
||||||
hash: Hash;
|
hash: Hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PathExpression extends Expression {
|
interface PathExpression extends Expression {
|
||||||
|
type: 'PathExpression';
|
||||||
data: boolean;
|
data: boolean;
|
||||||
depth: number;
|
depth: number;
|
||||||
parts: string[];
|
parts: string[];
|
||||||
@@ -336,29 +358,38 @@ declare namespace hbs {
|
|||||||
|
|
||||||
interface Literal extends Expression {}
|
interface Literal extends Expression {}
|
||||||
interface StringLiteral extends Literal {
|
interface StringLiteral extends Literal {
|
||||||
|
type: 'StringLiteral';
|
||||||
value: string;
|
value: string;
|
||||||
original: string;
|
original: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BooleanLiteral extends Literal {
|
interface BooleanLiteral extends Literal {
|
||||||
|
type: 'BooleanLiteral';
|
||||||
value: boolean;
|
value: boolean;
|
||||||
original: boolean;
|
original: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NumberLiteral extends Literal {
|
interface NumberLiteral extends Literal {
|
||||||
|
type: 'NumberLiteral';
|
||||||
value: number;
|
value: number;
|
||||||
original: 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 {
|
interface Hash extends Node {
|
||||||
|
type: 'Hash';
|
||||||
pairs: HashPair[];
|
pairs: HashPair[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HashPair extends Node {
|
interface HashPair extends Node {
|
||||||
|
type: 'HashPair';
|
||||||
key: string;
|
key: string;
|
||||||
value: Expression;
|
value: Expression;
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-1
@@ -110,4 +110,133 @@ Handlebars.compile('test', {
|
|||||||
|
|
||||||
Handlebars.compile('test')({},{allowCallsToHelperMissing: true});
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
createBuild({
|
||||||
|
name: 'umd-compiler-runtime',
|
||||||
|
entry: './lib/handlebars.js',
|
||||||
|
output: buildUmdOutput('handlebars.js'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: false
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'umd-compiler-runtime-min',
|
||||||
|
entry: './lib/handlebars.js',
|
||||||
|
output: buildUmdOutput('handlebars.min.js'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: true
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'umd-runtime',
|
||||||
|
entry: './lib/handlebars.runtime.js',
|
||||||
|
output: buildUmdOutput('handlebars.runtime.js'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: false
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'umd-runtime',
|
||||||
|
entry: './lib/handlebars.runtime.js',
|
||||||
|
output: buildUmdOutput('handlebars.runtime.min.js'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: true
|
||||||
|
}),
|
||||||
|
|
||||||
|
createBuild({
|
||||||
|
name: 'amd-compiler-runtime',
|
||||||
|
entry: './lib/handlebars.js',
|
||||||
|
output: buildAmdOutput('handlebars.amd.js', 'handlebars'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: false
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'amd-compiler-runtime-min',
|
||||||
|
entry: './lib/handlebars.js',
|
||||||
|
output: buildAmdOutput('handlebars.amd.min.js', 'handlebars'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: true
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'amd-runtime',
|
||||||
|
entry: './lib/handlebars.runtime.js',
|
||||||
|
output: buildAmdOutput('handlebars.runtime.amd.js', 'handlebars.runtime'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: false
|
||||||
|
}),
|
||||||
|
createBuild({
|
||||||
|
name: 'amd-runtime-min',
|
||||||
|
entry: './lib/handlebars.runtime.js',
|
||||||
|
output: buildAmdOutput('handlebars.runtime.amd.min.js', 'handlebars.runtime'),
|
||||||
|
shouldHandlebarsSupportSourceMaps: false,
|
||||||
|
minimize: true
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
function createBuild({name, entry, shouldHandlebarsSupportSourceMaps, output, minimize}) {
|
||||||
|
assert(output != null);
|
||||||
|
assert(name != null);
|
||||||
|
assert(entry != null);
|
||||||
|
assert(shouldHandlebarsSupportSourceMaps != null);
|
||||||
|
assert(minimize != null);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.m?js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: buildPluginConfig(shouldHandlebarsSupportSourceMaps),
|
||||||
|
mode: 'production',
|
||||||
|
devtool: false,
|
||||||
|
optimization: {
|
||||||
|
minimize
|
||||||
|
},
|
||||||
|
entry,
|
||||||
|
output
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPluginConfig(shouldHandlebarsSupportSourceMaps) {
|
||||||
|
const plugins = [];
|
||||||
|
if (!shouldHandlebarsSupportSourceMaps) {
|
||||||
|
plugins.push(
|
||||||
|
new webpack.IgnorePlugin({
|
||||||
|
resourceRegExp: /source-map/
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAmdOutput(outputFile, targetModuleName) {
|
||||||
|
return {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: targetModuleName,
|
||||||
|
libraryTarget: 'amd',
|
||||||
|
filename: outputFile,
|
||||||
|
globalObject: 'this',
|
||||||
|
sourceMapFilename: '[file].map',
|
||||||
|
libraryExport: 'default'
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUmdOutput(outputFile) {
|
||||||
|
return {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'Handlebars',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
filename: outputFile,
|
||||||
|
globalObject: 'this',
|
||||||
|
sourceMapFilename: '[file].map',
|
||||||
|
libraryExport: 'default'
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user