Merge branch 'decorators'

This commit is contained in:
kpdecker
2015-09-01 00:48:20 -05:00
21 changed files with 453 additions and 19 deletions
+1 -1
View File
@@ -45,7 +45,7 @@
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-func-assign": 2,
"no-func-assign": 0,
// Stylistic... might consider disallowing in the future
"no-inner-declarations": 0,
+28 -1
View File
@@ -120,6 +120,33 @@ interface CommentStatement <: Statement {
}
```
```java
interface Decorator <: Statement {
type: "Decorator";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
strip: StripFlags | null;
}
interface DecoratorBlock <: Statement {
type: "DecoratorBlock";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
### Expressions
```java
@@ -249,7 +276,7 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
- `parent` is the existing code in the path resolution
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
+16 -1
View File
@@ -1,6 +1,7 @@
import {createFrame, extend, toString} from './utils';
import Exception from './exception';
import {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import logger from './logger';
export const VERSION = '3.0.1';
@@ -17,11 +18,13 @@ export const REVISION_CHANGES = {
const objectType = '[object Object]';
export function HandlebarsEnvironment(helpers, partials) {
export function HandlebarsEnvironment(helpers, partials, decorators) {
this.helpers = helpers || {};
this.partials = partials || {};
this.decorators = decorators || {};
registerDefaultHelpers(this);
registerDefaultDecorators(this);
}
HandlebarsEnvironment.prototype = {
@@ -54,6 +57,18 @@ HandlebarsEnvironment.prototype = {
},
unregisterPartial: function(name) {
delete this.partials[name];
},
registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function(name) {
delete this.decorators[name];
}
};
+3
View File
@@ -69,6 +69,9 @@ function CodeGen(srcFile) {
}
CodeGen.prototype = {
isEmpty() {
return !this.source.length;
},
prepend: function(source, loc) {
this.source.unshift(this.wrap(source, loc));
},
+13
View File
@@ -156,6 +156,15 @@ Compiler.prototype = {
this.opcode('append');
},
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
},
PartialStatement: function(partial) {
this.usePartial = true;
@@ -201,6 +210,10 @@ Compiler.prototype = {
this.opcode('append');
}
},
Decorator(decorator) {
this.DecoratorBlock(decorator);
},
ContentStatement: function(content) {
if (content.value) {
+9 -2
View File
@@ -84,8 +84,9 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = (/\*/.test(open));
return {
type: 'MustacheStatement',
type: decorator ? 'Decorator' : 'MustacheStatement',
path,
params,
hash,
@@ -124,12 +125,18 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
validateClose(openBlock, close);
}
let decorator = (/\*/.test(openBlock.open));
program.blockParams = openBlock.blockParams;
let inverse,
inverseStrip;
if (inverseAndProgram) {
if (decorator) {
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
}
if (inverseAndProgram.chain) {
inverseAndProgram.program.body[0].closeStrip = close.strip;
}
@@ -145,7 +152,7 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
}
return {
type: 'BlockStatement',
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
path: openBlock.path,
params: openBlock.params,
hash: openBlock.hash,
+67 -4
View File
@@ -64,6 +64,7 @@ JavaScriptCompiler.prototype = {
this.name = this.environment.name;
this.isChild = !!context;
this.context = context || {
decorators: [],
programs: [],
environments: []
};
@@ -81,7 +82,7 @@ JavaScriptCompiler.prototype = {
this.compileChildren(environment, options);
this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
let opcodes = environment.opcodes,
@@ -107,16 +108,43 @@ JavaScriptCompiler.prototype = {
throw new Exception('Compile completed with content left on stack');
}
if (!this.decorators.isEmpty()) {
this.useDecorators = true;
this.decorators.prepend('var decorators = container.decorators;\n');
this.decorators.push('return fn;');
if (asObject) {
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
} else {
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
this.decorators.push('}\n');
this.decorators = this.decorators.merge();
}
} else {
this.decorators = undefined;
}
let fn = this.createFunctionContext(asObject);
if (!this.isChild) {
let ret = {
compiler: this.compilerInfo(),
main: fn
};
let programs = this.context.programs;
if (this.decorators) {
ret.main_d = this.decorators; // eslint-disable-line camelcase
ret.useDecorators = true;
}
let {programs, decorators} = this.context;
for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) {
ret[i] = programs[i];
if (decorators[i]) {
ret[i + '_d'] = decorators[i];
ret.useDecorators = true;
}
}
}
@@ -163,6 +191,7 @@ JavaScriptCompiler.prototype = {
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = new CodeGen(this.options.srcName);
this.decorators = new CodeGen(this.options.srcName);
},
createFunctionContext: function(asObject) {
@@ -561,6 +590,24 @@ JavaScriptCompiler.prototype = {
}
},
// [registerDecorator]
//
// On stack, before: hash, program, params..., ...
// On stack, after: ...
//
// Pops off the decorator's parameters, invokes the decorator,
// and inserts the decorator into the decorators list.
registerDecorator(paramSize, name) {
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
options = this.setupHelperArgs(name, paramSize);
this.decorators.push([
'fn = ',
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
' || fn;'
]);
},
// [invokeHelper]
//
// On stack, before: hash, inverse, program, params..., ...
@@ -738,6 +785,7 @@ JavaScriptCompiler.prototype = {
child.index = index;
child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child;
this.useDepths = this.useDepths || compiler.useDepths;
@@ -946,7 +994,16 @@ JavaScriptCompiler.prototype = {
},
setupParams: function(helper, paramSize, params) {
let options = {}, contexts = [], types = [], ids = [], param;
let options = {},
contexts = [],
types = [],
ids = [],
objectArgs = !params,
param;
if (objectArgs) {
params = [];
}
options.name = this.quotedString(helper);
options.hash = this.popStack();
@@ -985,6 +1042,10 @@ JavaScriptCompiler.prototype = {
}
}
if (objectArgs) {
options.args = this.source.generateArray(params);
}
if (this.trackIds) {
options.ids = this.source.generateArray(ids);
}
@@ -1009,9 +1070,11 @@ JavaScriptCompiler.prototype = {
this.useRegister('options');
params.push('options');
return ['options=', options];
} else {
} else if (params) {
params.push(options);
return '';
} else {
return options;
}
}
};
+6 -2
View File
@@ -48,11 +48,15 @@ PrintVisitor.prototype.Program = function(program) {
PrintVisitor.prototype.MustacheStatement = function(mustache) {
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.BlockStatement = function(block) {
PrintVisitor.prototype.BlockStatement =
PrintVisitor.prototype.DecoratorBlock = function(block) {
let out = '';
out += this.pad('BLOCK:');
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
this.padding++;
out += this.pad(this.SubExpression(block));
if (block.program) {
+2
View File
@@ -76,8 +76,10 @@ Visitor.prototype = {
},
MustacheStatement: visitSubExpression,
Decorator: visitSubExpression,
BlockStatement: visitBlock,
DecoratorBlock: visitBlock,
PartialStatement: visitPartial,
PartialBlockStatement: function(partial) {
@@ -63,6 +63,7 @@ WhitespaceControl.prototype.Program = function(program) {
};
WhitespaceControl.prototype.BlockStatement =
WhitespaceControl.prototype.DecoratorBlock =
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
this.accept(block.program);
this.accept(block.inverse);
@@ -124,6 +125,7 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
return strip;
};
WhitespaceControl.prototype.Decorator =
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
return mustache.strip;
};
+6
View File
@@ -0,0 +1,6 @@
import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
+22
View File
@@ -0,0 +1,22 @@
import {extend} from '../utils';
export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) {
let ret = fn;
if (!props.partials) {
props.partials = {};
ret = function(context, options) {
// Create a new partials stack frame prior to exec.
let original = container.partials;
container.partials = extend({}, original, props.partials);
let ret = fn(context, options);
container.partials = original;
return ret;
};
}
props.partials[options.args[0]] = options.fn;
return ret;
});
}
+30 -2
View File
@@ -29,6 +29,8 @@ export function template(templateSpec, env) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
templateSpec.main.decorator = templateSpec.main_d;
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
@@ -90,7 +92,9 @@ export function template(templateSpec, env) {
invokePartial: invokePartialWrapper,
fn: function(i) {
return templateSpec[i];
let ret = templateSpec[i];
ret.decorator = templateSpec[i + '_d'];
return ret;
},
programs: [],
@@ -142,7 +146,11 @@ export function template(templateSpec, env) {
}
}
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
function main(context/*, options*/) {
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
}
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
return main(context, options);
}
ret.isTop = true;
@@ -153,9 +161,13 @@ export function template(templateSpec, env) {
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
}
if (templateSpec.useDecorators) {
container.decorators = container.merge(options.decorators, env.decorators);
}
} else {
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
}
};
@@ -186,6 +198,9 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
blockParams && [options.blockParams].concat(blockParams),
currentDepths);
}
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
prog.program = i;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
@@ -216,6 +231,10 @@ export function invokePartial(partial, context, options) {
let partialBlock;
if (options.fn && options.fn !== noop) {
partialBlock = options.data['partial-block'] = options.fn;
if (partialBlock.partials) {
options.partials = Utils.extend({}, options.partials, partialBlock.partials);
}
}
if (partial === undefined && partialBlock) {
@@ -238,3 +257,12 @@ function initData(context, data) {
}
return data;
}
function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) {
let props = {};
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
Utils.extend(prog, props);
}
return prog;
}
+178
View File
@@ -166,4 +166,182 @@ describe('blocks', function() {
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
});
});
describe('decorators', function() {
it('should apply mustache decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
decorator: function(fn) {
fn.run = 'success';
return fn;
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
});
it('should apply allow undefined return', function() {
var helpers = {
helper: function(options) {
return options.fn() + options.fn.run;
}
};
var decorators = {
decorator: function(fn) {
fn.run = 'cess';
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}suc{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
});
it('should apply block decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
decorator: function(fn, props, container, options) {
fn.run = options.fn();
return fn;
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
});
it('should support nested decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
decorator: function(fn, props, container, options) {
fn.run = options.fn.nested + options.fn();
return fn;
},
nested: function(fn, props, container, options) {
props.nested = options.fn();
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
});
it('should apply multiple decorators', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
decorator: function(fn, props, container, options) {
fn.run = (fn.run || '') + options.fn();
return fn;
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
});
it('should access parent variables', function() {
var helpers = {
helper: function(options) {
return options.fn.run;
}
};
var decorators = {
decorator: function(fn, props, container, options) {
fn.run = options.args;
return fn;
}
};
shouldCompileTo(
'{{#helper}}{{*decorator foo}}{{/helper}}',
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
'success');
});
it('should work with root program', function() {
var run;
var decorators = {
decorator: function(fn, props, container, options) {
equals(options.args[0], 'success');
run = true;
return fn;
}
};
shouldCompileTo(
'{{*decorator "success"}}',
{hash: {'foo': 'success'}, decorators: decorators},
'');
equals(run, true);
});
it('should fail when accessing variables from root', function() {
var run;
var decorators = {
decorator: function(fn, props, container, options) {
equals(options.args[0], undefined);
run = true;
return fn;
}
};
shouldCompileTo(
'{{*decorator foo}}',
{hash: {'foo': 'fail'}, decorators: decorators},
'');
equals(run, true);
});
describe('registration', function() {
it('unregisters', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator('foo', function() {
return 'fail';
});
equals(!!handlebarsEnv.decorators.foo, true);
handlebarsEnv.unregisterDecorator('foo');
equals(handlebarsEnv.decorators.foo, undefined);
});
it('allows multiple globals', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator({
foo: function() {},
bar: function() {}
});
equals(!!handlebarsEnv.decorators.foo, true);
equals(!!handlebarsEnv.decorators.bar, true);
handlebarsEnv.unregisterDecorator('foo');
handlebarsEnv.unregisterDecorator('bar');
equals(handlebarsEnv.decorators.foo, undefined);
equals(handlebarsEnv.decorators.bar, undefined);
});
it('fails with multiple and args', function() {
shouldThrow(function() {
handlebarsEnv.registerDecorator({
world: function() { return 'world!'; },
testHelper: function() { return 'found it!'; }
}, {});
}, Error, 'Arg not supported with multiple decorators');
});
});
});
});
+14
View File
@@ -247,6 +247,20 @@ describe('parser', function() {
});
});
describe('directives', function() {
it('should parse block directives', function() {
equals(astFor('{{#* foo}}{{/foo}}'), 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
});
it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
});
it('should fail if directives have inverse', function() {
shouldThrow(function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
}, Error, /Unexpected inverse/);
});
});
it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse('\n'
+ ' {{#if foo}}\n'
+39
View File
@@ -257,6 +257,45 @@ describe('partials', function() {
});
});
describe('inline partials', function() {
it('should define inline partials for template', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
});
it('should overwrite multiple partials in the same template', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
});
it('should define inline partials for block', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
shouldThrow(function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', {}, 'success');
}, Error, /myPartial could not/);
});
it('should override global partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {hash: {}, partials: {myPartial: function() { return 'fail'; }}}, 'success');
});
it('should override template partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
});
it('should override partials down the entire stack', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', {}, 'success');
});
it('should define inline partials for partial call', function() {
shouldCompileToWithPartials(
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
true,
'success');
});
it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
true,
'success');
});
});
it('should pass compiler flags', function() {
if (Handlebars.compile) {
var env = Handlebars.create();
+3 -3
View File
@@ -14,19 +14,19 @@ describe('runtime', function() {
it('should throw on version mismatch', function() {
shouldThrow(function() {
Handlebars.template({
main: true,
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: true,
main: {},
compiler: [Handlebars.COMPILER_REVISION - 1]
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: true
main: {}
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
});
+9
View File
@@ -241,6 +241,15 @@ describe('Tokenizer', function() {
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
});
it('tokenizes directives', function() {
shouldMatchTokens(
tokenize('{{#*foo}}content{{/foo}}'),
['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
shouldMatchTokens(
tokenize('{{*foo}}'),
['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes inverse sections as "INVERSE"', function() {
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
+2
View File
@@ -9,6 +9,8 @@ describe('Visitor', function() {
var visitor = new Handlebars.Visitor();
visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'));
visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{* bar }}'));
});
it('should traverse to stubs', function() {
+2 -2
View File
@@ -81,7 +81,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
}
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
<mu>"{{"{LEFT_STRIP}?"#>" return 'OPEN_PARTIAL_BLOCK';
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
<mu>"{{"{LEFT_STRIP}?"#""*"? return 'OPEN_BLOCK';
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
@@ -98,7 +98,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
this.popState();
return 'COMMENT';
}
<mu>"{{"{LEFT_STRIP}? return 'OPEN';
<mu>"{{"{LEFT_STRIP}?"*"? return 'OPEN';
<mu>"=" return 'EQUALS';
<mu>".." return 'ID';
+1 -1
View File
@@ -52,7 +52,7 @@ block
;
openBlock
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { open: $1, path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
openInverse