Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to disable whitespace control: Handlebars.parseWithoutProcessing #1584

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/compiler-api.md
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
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
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
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, ' ');
});
});
rwjblue marked this conversation as resolved.
Show resolved Hide resolved
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
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
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>');
}