Repository: mozilla/nunjucks
Pull request #661 (2016-02-02 18:06:45)

Support using `in` operator to search in both arrays and objects.
Support using `in` operator to search in both arrays and objects, and it will throw an error for other data types.

This will fix #659.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f2f400..ff11689 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ Changelog
 master (unreleased)
 -------------------
 
+* Support using `in` operator to search in both arrays and objects,
+  and it will throw an error for other data types.
+  Fix [#659](https://github.com/mozilla/nunjucks/pull/659).
+  Thanks Alex Mayfield for report and test.
+  Merge of [#661](https://github.com/mozilla/nunjucks/pull/661).
+
 * Fix `{% set %}` scoping within macros.
   Fixes [#577](https://github.com/mozilla/nunjucks/issues/577) and
   the macro portion of [#561](https://github.com/mozilla/nunjucks/issues/561).
diff --git a/src/compiler.js b/src/compiler.js
index 911b13b..5c0ebbb 100644
--- a/src/compiler.js
+++ b/src/compiler.js
@@ -364,11 +364,11 @@ var Compiler = Object.extend({
     },
 
     compileIn: function(node, frame) {
-      this.emit('(');
-      this.compile(node.right, frame);
-      this.emit('.indexOf(');
+      this.emit('runtime.inOperator(');
       this.compile(node.left, frame);
-      this.emit(') !== -1)');
+      this.emit(',');
+      this.compile(node.right, frame);
+      this.emit(')');
     },
 
     compileOr: binOpEmitter(' || '),
diff --git a/src/lib.js b/src/lib.js
index d5d35b7..4024480 100644
--- a/src/lib.js
+++ b/src/lib.js
@@ -284,3 +284,14 @@ exports.keys = function(obj) {
         return keys;
     }
 };
+
+exports.inOperator = function (key, arrOrObj) {
+    if (exports.isArray(arrOrObj)) {
+        return exports.indexOf(arrOrObj, key) !== -1;
+    } else if (exports.isObject(arrOrObj)) {
+        return key in arrOrObj;
+    } else {
+        throw new Error('Cannot use "in" operator to search for "'
+            + key + '" in unexpected types.');
+    }
+};
diff --git a/src/runtime.js b/src/runtime.js
index 47f0399..6ef8f74 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -356,5 +356,6 @@ module.exports = {
     copySafeness: copySafeness,
     markSafe: markSafe,
     asyncEach: asyncEach,
-    asyncAll: asyncAll
+    asyncAll: asyncAll,
+    inOperator: lib.inOperator
 };


Test patch:
diff --git a/tests/compiler.js b/tests/compiler.js
index 45e0eef..a6c6c37 100644
--- a/tests/compiler.js
+++ b/tests/compiler.js
@@ -127,6 +127,10 @@
                   { food: 'beer' },
                   'beer');
 
+            equal('{% if "pizza" in food %}yum{% endif %}',
+                  { food: {'pizza': true }},
+                  'yum');
+
             finish(done);
         });
 
@@ -398,6 +402,34 @@
             equal('{% if 1 not in [2, 3] %}yes{% endif %}', 'yes');
             equal('{% if "a" in vals %}yes{% endif %}',
                   {'vals': ['a', 'b']}, 'yes');
+            equal('{% if "a" in obj %}yes{% endif %}',
+                  {'obj': { a: true }}, 'yes');
+            equal('{% if "a" in obj %}yes{% endif %}',
+                  {'obj': { b: true }}, '');
+
+            render(
+                '{% if "a" in 1 %}yes{% endif %}',
+                {},
+                { noThrow: true },
+                function(err, res) {
+                    expect(res).to.be(undefined);
+                    expect(err).to.match(
+                        /Cannot use "in" operator to search for "a" in unexpected types\./
+                    );
+                }
+            );
+
+            render(
+                '{% if "a" in obj %}yes{% endif %}',
+                {},
+                { noThrow: true },
+                function(err, res) {
+                    expect(res).to.be(undefined);
+                    expect(err).to.match(
+                        /Cannot use "in" operator to search for "a" in unexpected types\./
+                    );
+                }
+            );
 
             finish(done);
         });