Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/compiler-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion lib/handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,6 +25,7 @@ function create() {
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;

return hb;
}
Expand Down
12 changes: 10 additions & 2 deletions lib/handlebars/compiler/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -19,6 +19,14 @@ export function parse(input, options) {
return new yy.SourceLocation(options && options.srcName, locInfo);
};

let ast = parser.parse(input);

return ast;
}

export function parse(input, options) {
let ast = parseWithoutProcessing(input, options);
let strip = new WhitespaceControl(options);
return strip.accept(parser.parse(input));

return strip.accept(ast);
}
105 changes: 105 additions & 0 deletions spec/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,40 @@ describe('ast', function() {
});
});

describe('whitespace control', function() {
describe('parse', function() {
it('mustache', function() {
let 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() {
let 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() {
Expand All @@ -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}} '),
Expand Down Expand Up @@ -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 }} ');
Expand All @@ -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 }} ');
Expand Down
5 changes: 3 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -69,6 +69,7 @@ declare namespace Handlebars {
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>;
Expand Down
10 changes: 9 additions & 1 deletion types/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,12 @@ 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>');
}