From 78ed7a0839c70f248d898e009ddb919c06a6bd00 Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Wed, 4 Jan 2017 18:23:52 -0500 Subject: [PATCH 1/8] prevent value from being returned by Context.prototype.lookup if lookupHit is false --- mustache.js | 16 +++++++++------- test/_files/dot_notation.mustache | 1 + test/_files/dot_notation.txt | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/mustache.js b/mustache.js index 413b6f25e..56d6d6f86 100644 --- a/mustache.js +++ b/mustache.js @@ -377,11 +377,11 @@ if (cache.hasOwnProperty(name)) { value = cache[name]; } else { - var context = this, names, index, lookupHit = false; + var context = this, intermediateValue, names, index, lookupHit = false; while (context) { if (name.indexOf('.') > 0) { - value = context.view; + intermediateValue = context.view; names = name.split('.'); index = 0; @@ -396,19 +396,21 @@ * This is specially necessary for when the value has been set to * `undefined` and we want to avoid looking up parent contexts. **/ - while (value != null && index < names.length) { + while (intermediateValue != null && index < names.length) { if (index === names.length - 1) - lookupHit = hasProperty(value, names[index]); + lookupHit = hasProperty(intermediateValue, names[index]); - value = value[names[index++]]; + intermediateValue = intermediateValue[names[index++]]; } } else { - value = context.view[name]; + intermediateValue = context.view[name]; lookupHit = hasProperty(context.view, name); } - if (lookupHit) + if (lookupHit) { + value = intermediateValue; break; + } context = context.parent; } diff --git a/test/_files/dot_notation.mustache b/test/_files/dot_notation.mustache index f89d70ba2..264051453 100644 --- a/test/_files/dot_notation.mustache +++ b/test/_files/dot_notation.mustache @@ -7,3 +7,4 @@

Test truthy false values:

Zero: {{truthy.zero}}

False: {{truthy.notTrue}}

+

length of string should not be rendered: {{price.currency.name.length}}

diff --git a/test/_files/dot_notation.txt b/test/_files/dot_notation.txt index 08afa0529..eeb346d9a 100644 --- a/test/_files/dot_notation.txt +++ b/test/_files/dot_notation.txt @@ -7,3 +7,4 @@

Test truthy false values:

Zero: 0

False: false

+

length of string should not be rendered:

From a51e1c22a1194ab00e5ef32682d9f961475e7a32 Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Sat, 6 May 2017 22:13:54 -0400 Subject: [PATCH 2/8] add test for renderability of Array.length via dot notation --- test/_files/dot_notation.mustache | 1 + test/_files/dot_notation.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/test/_files/dot_notation.mustache b/test/_files/dot_notation.mustache index 264051453..8b6b2e9f4 100644 --- a/test/_files/dot_notation.mustache +++ b/test/_files/dot_notation.mustache @@ -8,3 +8,4 @@

Zero: {{truthy.zero}}

False: {{truthy.notTrue}}

length of string should not be rendered: {{price.currency.name.length}}

+

length of an array should be rendered: {{authors.length}}

diff --git a/test/_files/dot_notation.txt b/test/_files/dot_notation.txt index eeb346d9a..98d2e6ad5 100644 --- a/test/_files/dot_notation.txt +++ b/test/_files/dot_notation.txt @@ -8,3 +8,4 @@

Zero: 0

False: false

length of string should not be rendered:

+

length of an array should be rendered: 2

From 6382ec91e81f4e986384a6aadfad8cd471759388 Mon Sep 17 00:00:00 2001 From: David da Silva Date: Tue, 9 May 2017 12:54:10 +0200 Subject: [PATCH 3/8] Remove `typeof obj === 'object'` constraint in prop lookup Allows rendering properties of primitive types that are not objects, such as a string. --- mustache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mustache.js b/mustache.js index 56d6d6f86..52fa35488 100644 --- a/mustache.js +++ b/mustache.js @@ -42,7 +42,7 @@ * including its prototype, has a given property */ function hasProperty (obj, propName) { - return obj != null && typeof obj === 'object' && (propName in obj); + return obj != null && (propName in obj); } // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 From 81080456dd8af73fce57c458474620a5f54db59b Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Wed, 10 May 2017 08:03:42 -0400 Subject: [PATCH 4/8] pop lookup needs to use hasOwnProperty for non-objs --- mustache.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mustache.js b/mustache.js index 52fa35488..069c490e1 100644 --- a/mustache.js +++ b/mustache.js @@ -42,7 +42,10 @@ * including its prototype, has a given property */ function hasProperty (obj, propName) { - return obj != null && (propName in obj); + return ( + (obj != null && typeof obj === 'object' && (propName in obj)) + || (obj !== undefined && obj.hasOwnProperty(propName)) + ); } // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 From d2f3a480285d5bd72e121cbf223d729bf16380aa Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Wed, 10 May 2017 08:22:17 -0400 Subject: [PATCH 5/8] re-add constraint in prop lookup, but make property lookups for primitives possible through dot notation --- mustache.js | 19 ++++++++++++++++--- test/_files/dot_notation.mustache | 2 +- test/_files/dot_notation.txt | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/mustache.js b/mustache.js index 069c490e1..e45861f2c 100644 --- a/mustache.js +++ b/mustache.js @@ -42,9 +42,19 @@ * including its prototype, has a given property */ function hasProperty (obj, propName) { + return obj != null && typeof obj === 'object' && (propName in obj); + } + + /** + * Safe way of detecting whether or not the given thing is a primitive and + * whether it has the given property + */ + function primitiveHasOwnProperty (primitive, propName) { return ( - (obj != null && typeof obj === 'object' && (propName in obj)) - || (obj !== undefined && obj.hasOwnProperty(propName)) + typeof primitive !== 'object' + && primitive !== undefined + && primitive.hasOwnProperty + && primitive.hasOwnProperty(propName) ); } @@ -401,7 +411,10 @@ **/ while (intermediateValue != null && index < names.length) { if (index === names.length - 1) - lookupHit = hasProperty(intermediateValue, names[index]); + lookupHit = ( + hasProperty(intermediateValue, names[index]) + || primitiveHasOwnProperty(intermediateValue, names[index]) + ); intermediateValue = intermediateValue[names[index++]]; } diff --git a/test/_files/dot_notation.mustache b/test/_files/dot_notation.mustache index 8b6b2e9f4..f64e35050 100644 --- a/test/_files/dot_notation.mustache +++ b/test/_files/dot_notation.mustache @@ -7,5 +7,5 @@

Test truthy false values:

Zero: {{truthy.zero}}

False: {{truthy.notTrue}}

-

length of string should not be rendered: {{price.currency.name.length}}

+

length of string should be rendered: {{price.currency.name.length}}

length of an array should be rendered: {{authors.length}}

diff --git a/test/_files/dot_notation.txt b/test/_files/dot_notation.txt index 98d2e6ad5..f982a755e 100644 --- a/test/_files/dot_notation.txt +++ b/test/_files/dot_notation.txt @@ -7,5 +7,5 @@

Test truthy false values:

Zero: 0

False: false

-

length of string should not be rendered:

+

length of string should be rendered: 3

length of an array should be rendered: 2

From 0e607fe9f59db86298ca7da1aa2afbe3f00792ee Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Wed, 10 May 2017 08:34:12 -0400 Subject: [PATCH 6/8] add test to address #589 specifically --- test/_files/dot_notation.js | 3 ++- test/_files/dot_notation.mustache | 1 + test/_files/dot_notation.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/_files/dot_notation.js b/test/_files/dot_notation.js index de06a03a5..955651fe6 100644 --- a/test/_files/dot_notation.js +++ b/test/_files/dot_notation.js @@ -19,5 +19,6 @@ truthy: { zero: 0, notTrue: false - } + }, + singletonList: [{singletonItem: "singleton item"}] }) diff --git a/test/_files/dot_notation.mustache b/test/_files/dot_notation.mustache index f64e35050..1ef2209fe 100644 --- a/test/_files/dot_notation.mustache +++ b/test/_files/dot_notation.mustache @@ -8,4 +8,5 @@

Zero: {{truthy.zero}}

False: {{truthy.notTrue}}

length of string should be rendered: {{price.currency.name.length}}

+

length of string in a list should be rendered: {{#singletonList}}{{singletonItem.length}}{{/singletonList}}

length of an array should be rendered: {{authors.length}}

diff --git a/test/_files/dot_notation.txt b/test/_files/dot_notation.txt index f982a755e..c71ab27dd 100644 --- a/test/_files/dot_notation.txt +++ b/test/_files/dot_notation.txt @@ -8,4 +8,5 @@

Zero: 0

False: false

length of string should be rendered: 3

+

length of string in a list should be rendered: 14

length of an array should be rendered: 2

From fa75abc4cdbee9c4aad850fa48e863b2c1b2a59e Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Wed, 31 May 2017 21:55:54 -0400 Subject: [PATCH 7/8] enhance readability of primitiveHasOwnProperty and add comments to explain why it is used in one case but not the other --- mustache.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/mustache.js b/mustache.js index e45861f2c..433714b4f 100644 --- a/mustache.js +++ b/mustache.js @@ -51,8 +51,8 @@ */ function primitiveHasOwnProperty (primitive, propName) { return ( - typeof primitive !== 'object' - && primitive !== undefined + primitive != null + && typeof primitive !== 'object' && primitive.hasOwnProperty && primitive.hasOwnProperty(propName) ); @@ -408,6 +408,12 @@ * * This is specially necessary for when the value has been set to * `undefined` and we want to avoid looking up parent contexts. + * + * In the case where dot notation is used, we consider the lookup + * to be successful even if the last "object" in the path is + * not actually an object but a primitive (e.g., a string, or an + * integer), because it is sometimes useful to access a property + * of an autoboxed primitive, such as the length of a string. **/ while (intermediateValue != null && index < names.length) { if (index === names.length - 1) @@ -420,6 +426,19 @@ } } else { intermediateValue = context.view[name]; + + /** + * Only checking against hasProperty, which always returns false if + * context.view is not an object. Deliberately omitting the check + * against primitiveHasOwnProperty if dot notation is not used. + * + * Consider this example: + * Mustache.render("{{#length}}{{length}}{{/length}}", {length: "hello"}) + * + * If we were to check also against primitiveHasOwnProperty, as we do + * in the dot notation case, then render call would return 5, rather + * than the expected "hello". + **/ lookupHit = hasProperty(context.view, name); } From bd12d95eb57b531f303c36246da9e39a3ad84e0b Mon Sep 17 00:00:00 2001 From: "raymond.lam" Date: Sun, 4 Jun 2017 16:42:39 -0400 Subject: [PATCH 8/8] wrap code comments explaining , and use a different example --- mustache.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/mustache.js b/mustache.js index 433714b4f..b548d7db3 100644 --- a/mustache.js +++ b/mustache.js @@ -428,16 +428,23 @@ intermediateValue = context.view[name]; /** - * Only checking against hasProperty, which always returns false if - * context.view is not an object. Deliberately omitting the check - * against primitiveHasOwnProperty if dot notation is not used. + * Only checking against `hasProperty`, which always returns `false` if + * `context.view` is not an object. Deliberately omitting the check + * against `primitiveHasOwnProperty` if dot notation is not used. * * Consider this example: - * Mustache.render("{{#length}}{{length}}{{/length}}", {length: "hello"}) + * ``` + * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"}) + * ``` * - * If we were to check also against primitiveHasOwnProperty, as we do - * in the dot notation case, then render call would return 5, rather - * than the expected "hello". + * If we were to check also against `primitiveHasOwnProperty`, as we do + * in the dot notation case, then render call would return: + * + * "The length of a football field is 9." + * + * rather than the expected: + * + * "The length of a football field is 100 yards." **/ lookupHit = hasProperty(context.view, name); }