Merge branch '4.x'

# Conflicts:
#	components/bower.json
#	components/handlebars.js.nuspec
#	components/package.json
#	package-lock.json
#	package.json
This commit is contained in:
Nils Knappmeier
2019-11-09 06:47:10 +01:00
13 changed files with 369 additions and 31 deletions
+6 -18
View File
@@ -1,25 +1,11 @@
module.exports = {
"extends": "eslint:recommended",
"extends": ["eslint:recommended","plugin:compat/recommended"],
"globals": {
"self": false
},
"env": {
"node": true
},
"ecmaFeatures": {
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
"arrowFunctions": true,
"blockBindings": true,
"defaultParams": true,
"destructuring": true,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"restParams": true,
"spread": true,
"templateStrings": true
"node": true,
"es6": true
},
"rules": {
// overrides eslint:recommended defaults
@@ -124,6 +110,8 @@ module.exports = {
"no-var": "warn"
},
"parserOptions": {
"sourceType": "module"
"sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {}
}
}
+28
View File
@@ -16,6 +16,34 @@ var ast = Handlebars.parse(myTemplate);
Handlebars.precompile(ast);
```
### Parsing
There are two primary APIs that are used to parse an existing template into the AST:
#### parseWithoutProcessing
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
Example:
```js
let ast = Handlebars.parseWithoutProcessing(myTemplate);
```
#### parse
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
Example:
```js
let ast = Handlebars.parse(myTemplate);
```
### Basic
+2 -1
View File
@@ -2,7 +2,7 @@ import runtime from './handlebars.runtime';
// Compiler imports
import AST from './handlebars/compiler/ast';
import { parser as Parser, parse } from './handlebars/compiler/base';
import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor';
@@ -25,6 +25,7 @@ function create() {
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;
return hb;
}
+1 -1
View File
@@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import logger from './logger';
export const VERSION = '4.4.5';
export const VERSION = '4.5.1';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
+11 -3
View File
@@ -8,7 +8,7 @@ export { parser };
let yy = {};
extend(yy, Helpers);
export function parse(input, options) {
export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { return input; }
@@ -19,6 +19,14 @@ export function parse(input, options) {
return new yy.SourceLocation(options && options.srcName, locInfo);
};
let strip = new WhitespaceControl(options);
return strip.accept(parser.parse(input));
let ast = parser.parse(input);
return ast;
}
export function parse(input, options) {
let ast = parseWithoutProcessing(input, options);
let strip = new WhitespaceControl(options);
return strip.accept(ast);
}
+111
View File
@@ -4,6 +4,15 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"@knappi/grunt-saucelabs": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@knappi/grunt-saucelabs/-/grunt-saucelabs-9.0.2.tgz",
@@ -326,6 +335,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"ast-metadata-inferer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.1.1.tgz",
"integrity": "sha512-hc9w8Qrgg9Lf9iFcZVhNjUnhrd2BBpTlyCnegPVvCe6O0yMrF57a6Cmh7k+xUsfUOMh9wajOL5AsGOBNEyTCcw==",
"dev": true
},
"ast-traverse": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz",
@@ -837,6 +852,17 @@
"pako": "~0.2.0"
}
},
"browserslist": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.2.tgz",
"integrity": "sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001004",
"electron-to-chromium": "^1.3.295",
"node-releases": "^1.1.38"
}
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
@@ -936,6 +962,18 @@
}
}
},
"caniuse-db": {
"version": "1.0.30001005",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001005.tgz",
"integrity": "sha512-MSRfm2N6FRDSpAJ00ipCuFe0CNink5JJOFzl4S7fLSBJdowhGq3uMxzkWGTjvvReo1PuWfK5YYJydJJ+9mJebw==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001005",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001005.tgz",
"integrity": "sha512-g78miZm1Z5njjYR216a5812oPiLgV1ssndgGxITHWUopmjUrCswMisA0a2kSB7a0vZRox6JOKhM51+efmYN8Mg==",
"dev": true
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -1781,6 +1819,12 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.296",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.296.tgz",
"integrity": "sha512-s5hv+TSJSVRsxH190De66YHb50pBGTweT9XGWYu/LMR20KX6TsjFzObo36CjVAzM+PUeeKSBRtm/mISlCzeojQ==",
"dev": true
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -2101,6 +2145,35 @@
}
}
},
"eslint-plugin-compat": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-3.3.0.tgz",
"integrity": "sha512-QCgYy3pZ+zH10dkBJus1xER0359h1UhJjufhQRqp9Owm6BEoLZeSqxf2zINwL1OGao9Yc96xPYIW3nQj5HUryg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.5",
"ast-metadata-inferer": "^0.1.1",
"browserslist": "^4.6.3",
"caniuse-db": "^1.0.30000977",
"lodash.memoize": "4.1.2",
"mdn-browser-compat-data": "^0.0.84",
"semver": "^6.1.2"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"eslint-plugin-es5": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-es5/-/eslint-plugin-es5-1.4.1.tgz",
"integrity": "sha512-kktkmkF2O7pnSZYgrMiYMbt3wCKRIiXePwILv8USDG95YgP0PzhIxSIROLLKmiQQ/Z6LuhDGWTHK04gnbXBvkg==",
"dev": true
},
"eslint-scope": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
@@ -5248,6 +5321,12 @@
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@@ -5339,6 +5418,15 @@
"pretty-bytes": "^1.0.0"
}
},
"mdn-browser-compat-data": {
"version": "0.0.84",
"resolved": "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.84.tgz",
"integrity": "sha512-fAznuGNaQMQiWLVf+gyp33FaABTglYWqMT7JqvH+4RZn2UQPD12gbMqxwP9m0lj8AAbNpu5/kD6n4Ox1SOffpw==",
"dev": true,
"requires": {
"extend": "3.0.2"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5742,6 +5830,23 @@
}
}
},
"node-releases": {
"version": "1.1.39",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.39.tgz",
"integrity": "sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==",
"dev": true,
"requires": {
"semver": "^6.3.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"nomnom": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz",
@@ -6874,6 +6979,12 @@
"through": "~2.3.8"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
"dev": true
},
"regex-cache": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+2
View File
@@ -37,6 +37,8 @@
"dtslint": "^0.5.5",
"dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3",
"eslint-plugin-compat": "^3.3.0",
"eslint-plugin-es5": "^1.4.1",
"grunt": "^1.0.3",
"grunt-babel": "^5.0.0",
"grunt-bg-shell": "^2.3.3",
+30 -1
View File
@@ -2,7 +2,36 @@
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...master)
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.1...master)
## v4.5.1 - October 29th, 2019
Bugfixs
- fix: move "eslint-plugin-compat" to devDependencies - 5e9d17f (#1589)
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.0...v4.5.1)
## v4.5.0 - October 28th, 2019
Features / Improvements
- Add method Handlebars.parseWithoutProcessing (#1584) - 62ed3c2
- add guard to if & unless helpers (#1549)
- show source location for the strict lookup exceptions - feb60f8
Bugfixes:
- Use objects for hash value tracking - 7fcf9d2
Chore:
- Resolve deprecation warning message from eslint while running eslint (#1586) - 7052e88
- chore: add eslint-plugin-compat and eslint-plugin-es5 - 088e618
Compatibility notes:
- No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...v4.5.0)
## v4.4.5 - October 20th, 2019
Bugfixes:
+7 -2
View File
@@ -1,14 +1,19 @@
{
"extends": [
"../.eslintrc.js",
"plugin:es5/no-es2015"
],
"plugins": [
"es5"
],
"globals": {
"CompilerContext": true,
"Handlebars": true,
"handlebarsEnv": true,
"shouldCompileTo": true,
"shouldCompileToWithPartials": true,
"shouldThrow": true,
"compileWithPartials": true,
"console": true,
"require": true,
"suite": true,
+105
View File
@@ -123,6 +123,40 @@ describe('ast', function() {
});
});
describe('whitespace control', function() {
describe('parse', function() {
it('mustache', function() {
var ast = Handlebars.parse(' {{~comment~}} ');
equals(ast.body[0].value, '');
equals(ast.body[2].value, '');
});
it('block statements', function() {
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
equals(ast.body[0].value, '');
equals(ast.body[1].program.body[0].value, 'foo');
});
});
describe('parseWithoutProcessing', function() {
it('mustache', function() {
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment~}} \nfoo\n {{~/comment}}');
equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
});
});
});
describe('standalone flags', function() {
describe('mustache', function() {
it('does not mark mustaches as standalone', function() {
@@ -131,6 +165,54 @@ describe('ast', function() {
equals(!!ast.body[2].value, true);
});
});
describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
equals(ast.body[0].value, ' ');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n ');
});
it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, ' \n');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n ');
});
it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, ' \n');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
});
it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
});
describe('blocks', function() {
it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
@@ -204,6 +286,18 @@ describe('ast', function() {
equals(ast.body[2].value, '');
});
});
describe('partials - parseWithoutProcessing', function() {
it('simple partial', function() {
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
equals(ast.body[1].value, ' ');
});
it('indented partial', function() {
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[1].indent, '');
equals(ast.body[2].value, ' ');
});
});
describe('partials', function() {
it('marks partial as standalone', function() {
var ast = Handlebars.parse('{{> partial }} ');
@@ -223,6 +317,17 @@ describe('ast', function() {
equals(ast.body[1].omit, undefined);
});
});
describe('comments - parseWithoutProcessing', function() {
it('simple comment', function() {
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
equals(ast.body[1].value, ' ');
});
it('indented comment', function() {
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
});
describe('comments', function() {
it('marks comment as standalone', function() {
var ast = Handlebars.parse('{{! comment }} ');
+1 -1
View File
@@ -9,7 +9,7 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
+17 -3
View File
@@ -47,8 +47,8 @@ declare namespace Handlebars {
}
export interface ParseOptions {
srcName?: string,
ignoreStandalone?: boolean
srcName?: string;
ignoreStandalone?: boolean;
}
export function registerHelper(name: string, fn: HelperDelegate): void;
@@ -66,9 +66,9 @@ declare namespace Handlebars {
export function K(): void;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function Exception(message: string): void;
export function log(level: number, obj: any): void;
export function parse(input: string, options?: ParseOptions): hbs.AST.Program;
export function parseWithoutProcessing(input: string, options?: ParseOptions): hbs.AST.Program;
export function compile<T = any>(input: any, options?: CompileOptions): HandlebarsTemplateDelegate<T>;
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
export function template<T = any>(precompilation: TemplateSpecification): HandlebarsTemplateDelegate<T>;
@@ -86,6 +86,20 @@ declare namespace Handlebars {
export function noConflict(): typeof Handlebars;
export class Exception {
constructor(message: string, node?: hbs.AST.Node);
description: string;
fileName: string;
lineNumber?: any;
endLineNumber?: any;
message: string;
name: string;
number: number;
stack?: string;
column?: any;
endColumn?: any;
}
export class SafeString {
constructor(str: string);
toString(): string;
+48 -1
View File
@@ -192,4 +192,51 @@ switch(allthings.type) {
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;
}