Repository: mozilla/nunjucks (js)

Issue:
obj.hasOwnProperty undefined is not a function nodejs
Hello,

I was noticing some discrepancies in some of the rendering of the nunjuck templates. After enough digging around I boiled it down to different nodejs versions between development and production. However the production machine has a more resent version of nodejs.

```
~ > node --version
v0.12.2
```

With version v0.12.x of node, this line

https://github.com/mozilla/nunjucks/blob/master/src/lib.js#L271

fails with **TypeError: undefined is not a function.**

Has anyone encountered this, is there any know workaround?


Pull request:
Support objects created with Object.create(null)
## Summary

Proposed change:

Support objects created with Object.create(null) by following the [no-prototype-builtins](http://eslint.org/docs/rules/no-prototype-builtins). This fix is important because it makes nunjucks compatible with this widely used ES5 feature (Object.create).
<!--
	Please replace this with a human-friendly description of your proposed change.
	* If you have multiple changes, try to split them into separate PRs.
	* If this change closes an issue, please write "Closes #{number}".
-->

Closes #468


## Checklist

I've completed the checklist below to ensure I didn't forget anything. This makes reviewing this PR as easy as possible for the maintainers. And it gets this change released as soon as possible.

* [x] Proposed change helps towards [*purpose of this project*](https://github.com/mozilla/nunjucks/blob/master/CONTRIBUTING.md#purpose).
* [x] [*Documentation*](https://github.com/mozilla/nunjucks/tree/master/docs/) is added / updated to describe proposed change.
* [x] [*Tests*](https://github.com/mozilla/nunjucks/tree/master/tests) are added / updated to cover proposed change.
* [x] [*Changelog*](https://github.com/mozilla/nunjucks/blob/master/CHANGELOG.md) has an entry for proposed change (if user-facing fix or feature).

Note no documentation update was necessary.
<!-- Tick of items by replacing `[ ]` by `[x]` -->

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9e847b..7f0897c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ Changelog
 master (unreleased)
 -------------------
 
+* Support objects created with Object.create(null). fixes [#468](https://github.com/mozilla/nunjucks/issues/468)
+  
 
 3.0.1 (May 24 2017)
 -------------------
diff --git a/src/compiler.js b/src/compiler.js
index 1efa47c..97b71d0 100644
--- a/src/compiler.js
+++ b/src/compiler.js
@@ -838,7 +838,7 @@ var Compiler = Object.extend({
             'var callerFrame = frame;',
             'frame = ' + ((keepFrame) ? 'frame.push(true);' : 'new runtime.Frame();'),
             'kwargs = kwargs || {};',
-            'if (kwargs.hasOwnProperty("caller")) {',
+            'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {',
             'frame.set("caller", kwargs.caller); }'
         );
 
@@ -856,7 +856,7 @@ var Compiler = Object.extend({
             lib.each(kwargs.children, function(pair) {
                 var name = pair.key.value;
                 this.emit('frame.set("' + name + '", ' +
-                          'kwargs.hasOwnProperty("' + name + '") ? ' +
+                          'Object.prototype.hasOwnProperty.call(kwargs, "' + name + '") ? ' +
                           'kwargs["' + name + '"] : ');
                 this._compileExpression(pair.value, frame);
                 this.emitLine(');');
@@ -954,7 +954,7 @@ var Compiler = Object.extend({
                 alias = name;
             }
 
-            this.emitLine('if(' + importedId + '.hasOwnProperty("' + name + '")) {');
+            this.emitLine('if(Object.prototype.hasOwnProperty.call(' + importedId + ', "' + name + '")) {');
             this.emitLine('var ' + id + ' = ' + importedId + '.' + name + ';');
             this.emitLine('} else {');
             this.emitLine('cb(new Error("cannot import \'' + name + '\'")); return;');
diff --git a/src/environment.js b/src/environment.js
index e5597a7..7a7ff67 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -339,7 +339,7 @@ var Context = Obj.extend({
         // Make a duplicate of ctx
         this.ctx = {};
         for(var k in ctx) {
-            if(ctx.hasOwnProperty(k)) {
+            if(Object.prototype.hasOwnProperty.call(ctx, k)) {
                 this.ctx[k] = ctx[k];
             }
         }
diff --git a/src/filters.js b/src/filters.js
index e2afa99..67eaf68 100644
--- a/src/filters.js
+++ b/src/filters.js
@@ -513,7 +513,7 @@ var filters = {
             } else {
                 parts = [];
                 for (var k in obj) {
-                    if (obj.hasOwnProperty(k)) {
+                    if (Object.prototype.hasOwnProperty.call(obj, k)) {
                         parts.push(enc(k) + '=' + enc(obj[k]));
                     }
                 }
diff --git a/src/lib.js b/src/lib.js
index 86bef9f..1a37a27 100644
--- a/src/lib.js
+++ b/src/lib.js
@@ -277,7 +277,7 @@ exports.keys = function(obj) {
     else {
         var keys = [];
         for(var k in obj) {
-            if(obj.hasOwnProperty(k)) {
+            if(Object.prototype.hasOwnProperty.call(obj, k)) {
                 keys.push(k);
             }
         }
diff --git a/src/runtime.js b/src/runtime.js
index 5b03d08..ca2f9eb 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -126,11 +126,15 @@ function makeKeywordArgs(obj) {
     return obj;
 }
 
+function isKeywordArgs(obj) {
+  return obj && Object.prototype.hasOwnProperty.call(obj, '__keywords');
+}
+
 function getKeywordArgs(args) {
     var len = args.length;
     if(len) {
         var lastArg = args[len - 1];
-        if(lastArg && lastArg.hasOwnProperty('__keywords')) {
+        if(isKeywordArgs(lastArg)) {
             return lastArg;
         }
     }
@@ -144,7 +148,7 @@ function numArgs(args) {
     }
 
     var lastArg = args[len - 1];
-    if(lastArg && lastArg.hasOwnProperty('__keywords')) {
+    if(isKeywordArgs(lastArg)) {
         return len - 1;
     }
     else {


Test patch:
diff --git a/tests/compiler.js b/tests/compiler.js
index 9355d69..d70a344 100644
--- a/tests/compiler.js
+++ b/tests/compiler.js
@@ -53,6 +53,22 @@
             finish(done);
         });
 
+        it('should compile references - object without prototype', function(done) {
+          var context = Object.create(null);
+          context.foo = Object.create(null);
+          context.foo.bar = 'baz';
+          
+          equal('{{ foo.bar }}',
+            context,
+            'baz');
+
+          equal('{{ foo["bar"] }}',
+            context,
+            'baz');
+
+          finish(done);
+        });
+
         it('should fail silently on undefined values', function(done) {
             equal('{{ foo }}', '');
             equal('{{ foo.bar }}', '');
diff --git a/tests/filters.js b/tests/filters.js
index ebc73ab..9bb5b3b 100644
--- a/tests/filters.js
+++ b/tests/filters.js
@@ -586,6 +586,15 @@
             finish(done);
         });
 
+        it('urlencode - object without prototype', function(done) {
+          var obj = Object.create(null);
+          obj['1'] = 2;
+          obj['&1'] = '&2';
+          
+          equal('{{ obj | urlencode | safe }}', { obj: obj}, '1=2&%261=%262');
+          finish(done);
+        });
+
         it('urlize', function(done) {
             // from jinja test suite:
             // https://github.com/mitsuhiko/jinja2/blob/8db47916de0e888dd8664b2511e220ab5ecf5c15/jinja2/testsuite/filters.py#L236-L239
diff --git a/tests/runtime.js b/tests/runtime.js
index 5aaa628..0ac462c 100644
--- a/tests/runtime.js
+++ b/tests/runtime.js
@@ -76,5 +76,22 @@
 
             finish(done);
         });
+
+      it('should allow for objects without a prototype macro arguments in the last position', function(done) {
+        var noProto = Object.create(null);
+        noProto.qux = 'world';
+        
+        render('{% macro foo(bar, baz) %}' +
+          '{{ bar }} {{ baz.qux }}{% endmacro %}' +
+          '{{ foo("hello", noProto) }}',
+          {noProto: noProto},
+          { noThrow: true },
+          function(err, res) {
+            expect(err).to.equal(null);
+            expect(res).to.equal('hello world');
+          });
+
+        finish(done);
+      });
     });
 })();