Add trackIds compiler flag

Allows helpers that care about where a particular field came from derive this data while maintaining backward compatibility with existing helpers.
This commit is contained in:
kpdecker
2014-01-17 19:14:36 -06:00
parent 9df919083d
commit ace2896ec8
4 changed files with 151 additions and 8 deletions
+5 -1
View File
@@ -149,7 +149,8 @@ var AST = {
var original = "",
dig = [],
depth = 0;
depth = 0,
depthString = '';
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i].part;
@@ -160,6 +161,7 @@ var AST = {
throw new Exception("Invalid path: " + original, this);
} else if (part === "..") {
depth++;
depthString += '../';
} else {
this.isScoped = true;
}
@@ -172,6 +174,7 @@ var AST = {
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.idName = depthString + this.string;
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
@@ -191,6 +194,7 @@ var AST = {
this.type = "DATA";
this.id = id;
this.stringModeValue = id.stringModeValue;
this.idName = '@' + id.stringModeValue;
},
StringNode: function(string, locInfo) {
+4
View File
@@ -74,6 +74,7 @@ Compiler.prototype = {
this.depths = {list: []};
this.options = options;
this.stringParams = options.stringParams;
this.trackIds = options.trackIds;
// These changes will propagate to the other compiler components
var knownHelpers = this.options.knownHelpers;
@@ -395,6 +396,9 @@ Compiler.prototype = {
this.sexpr(val);
}
} else {
if (this.trackIds) {
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
}
this.accept(val);
}
},
+36 -3
View File
@@ -61,6 +61,7 @@ JavaScriptCompiler.prototype = {
this.environment = environment;
this.options = options || {};
this.stringParams = this.options.stringParams;
this.trackIds = this.options.trackIds;
log('debug', this.environment.disassemble() + "\n\n");
@@ -418,6 +419,9 @@ JavaScriptCompiler.prototype = {
emptyHash: function() {
this.pushStackLiteral('{}');
if (this.trackIds) {
this.push('{}'); // hashIds
}
if (this.stringParams) {
this.push('{}'); // hashContexts
this.push('{}'); // hashTypes
@@ -427,12 +431,15 @@ JavaScriptCompiler.prototype = {
if (this.hash) {
this.hashes.push(this.hash);
}
this.hash = {values: [], types: [], contexts: []};
this.hash = {values: [], types: [], contexts: [], ids: []};
},
popHash: function() {
var hash = this.hash;
this.hash = this.hashes.pop();
if (this.trackIds) {
this.push('{' + hash.ids.join(',') + '}');
}
if (this.stringParams) {
this.push('{' + hash.contexts.join(',') + '}');
this.push('{' + hash.types.join(',') + '}');
@@ -589,8 +596,12 @@ JavaScriptCompiler.prototype = {
assignToHash: function(key) {
var value = this.popStack(),
context,
type;
type,
id;
if (this.trackIds) {
id = this.popStack();
}
if (this.stringParams) {
type = this.popStack();
context = this.popStack();
@@ -603,9 +614,22 @@ JavaScriptCompiler.prototype = {
if (type) {
hash.types.push("'" + key + "': " + type);
}
if (id) {
hash.ids.push("'" + key + "': " + id);
}
hash.values.push("'" + key + "': (" + value + ")");
},
pushId: function(type, name) {
if (type === 'ID' || type === 'DATA') {
this.pushString(name);
} else if (type === 'sexpr') {
this.pushStackLiteral('true');
} else {
this.pushStackLiteral('null');
}
},
// HELPERS
compiler: JavaScriptCompiler,
@@ -844,11 +868,14 @@ JavaScriptCompiler.prototype = {
},
setupOptions: function(helper, paramSize, params) {
var options = {}, contexts = [], types = [], param, inverse, program;
var options = {}, contexts = [], types = [], ids = [], param, inverse, program;
options.name = this.quotedString(helper);
options.hash = this.popStack();
if (this.trackIds) {
options.hashIds = this.popStack();
}
if (this.stringParams) {
options.hashTypes = this.popStack();
options.hashContexts = this.popStack();
@@ -878,12 +905,18 @@ JavaScriptCompiler.prototype = {
param = this.popStack();
params.push(param);
if (this.trackIds) {
ids.push(this.popStack());
}
if (this.stringParams) {
types.push(this.popStack());
contexts.push(this.popStack());
}
}
if (this.trackIds) {
options.ids = "[" + ids.join(",") + "]";
}
if (this.stringParams) {
options.types = "[" + types.join(",") + "]";
options.contexts = "[" + contexts.join(",") + "]";
+106 -4
View File
@@ -1,6 +1,108 @@
/*global CompilerContext */
describe('track ids', function() {
it('should include argument ids');
it('should include hash ids');
it('should note ../ references');
it('should update the path when using built helpers');
var context;
beforeEach(function() {
context = {is: {a: 'foo'}, slave: {driver: 'bar'}};
});
it('should not include anything without the flag', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}');
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids, undefined);
equal(options.hashIds, undefined);
return 'success';
}
};
equals(template({}, {helpers: helpers}), 'success');
});
it('should include argument ids', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {trackIds: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], 'is.a');
equal(options.ids[1], 'slave.driver');
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
});
it('should include hash ids', function() {
var template = CompilerContext.compile('{{wycats bat=is.a baz=slave.driver}}', {trackIds: true});
var helpers = {
wycats: function(options) {
equal(options.hashIds.bat, 'is.a');
equal(options.hashIds.baz, 'slave.driver');
return "HELP ME MY BOSS " + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz;
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
});
it('should note ../ and ./ references', function() {
var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver}}', {trackIds: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], 'is.a');
equal(options.ids[1], '../slave.driver');
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined');
});
it('should note @data references', function() {
var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {trackIds: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], '@is.a');
equal(options.ids[1], '@slave.driver');
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
}
};
equals(template({}, {helpers: helpers, data:context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar');
});
it('should return null for constants', function() {
var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {trackIds: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], null);
equal(options.ids[1], null);
equal(options.hashIds.key, null);
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun + ' ' + options.hash.key;
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1 foo false');
});
it('should return true for subexpressions', function() {
var template = CompilerContext.compile('{{wycats (sub)}}', {trackIds: true});
var helpers = {
sub: function() { return 1; },
wycats: function(passiveVoice, options) {
equal(options.ids[0], true);
return "HELP ME MY BOSS " + passiveVoice;
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
});
});