Skip to content

Commit 89fe612

Browse files
authored
Merge pull request #16218 from rwjblue/fix-get-with-numeric
[BUGFIX beta] Prevent errors when using const `(get arr 1)`.
2 parents f243404 + 706166a commit 89fe612

File tree

7 files changed

+96
-23
lines changed

7 files changed

+96
-23
lines changed

packages/ember-glimmer/lib/helpers/get.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,12 @@ class GetHelperReference extends CachedReference {
7979

8080
static create(sourceReference: VersionedPathReference<Opaque>, pathReference: PathReference<string>) {
8181
if (isConst(pathReference)) {
82-
let parts = pathReference.value().split('.');
83-
return referenceFromParts(sourceReference, parts);
82+
let value = pathReference.value();
83+
if (typeof value === 'string' && value.indexOf('.') > -1) {
84+
return referenceFromParts(sourceReference, value.split('.'));
85+
} else {
86+
return sourceReference.get(value);
87+
}
8488
} else {
8589
return new GetHelperReference(sourceReference, pathReference);
8690
}
@@ -108,10 +112,10 @@ class GetHelperReference extends CachedReference {
108112
if (path !== undefined && path !== null && path !== '') {
109113
let pathType = typeof path;
110114

111-
if (pathType === 'string') {
115+
if (pathType === 'string' && path.indexOf('.') > -1) {
112116
innerReference = referenceFromParts(this.sourceReference, path.split('.'));
113-
} else if (pathType === 'number') {
114-
innerReference = this.sourceReference.get('' + path);
117+
} else {
118+
innerReference = this.sourceReference.get(path);
115119
}
116120

117121
innerTag.inner.update(innerReference.tag);

packages/ember-glimmer/tests/integration/helpers/get-test.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ moduleFor('Helpers test: {{get}}', class extends RenderingTest {
5050
this.assertText('[red and yellow] [red and yellow]');
5151
}
5252

53-
['@test should be able to get an object value with numeric keys']() {
54-
this.render(`{{#each indexes as |index|}}[{{get items index}}]{{/each}}`, {
53+
['@test should be able to get an object value with a number']() {
54+
this.render(`[{{get items 1}}][{{get items 2}}][{{get items 3}}]`, {
5555
indexes: [1, 2, 3],
5656
items: {
5757
1: 'First',
@@ -75,8 +75,52 @@ moduleFor('Helpers test: {{get}}', class extends RenderingTest {
7575
this.assertText('[First][Second][Third]');
7676
}
7777

78+
['@test should be able to get an array value with a number']() {
79+
this.render(`[{{get numbers 0}}][{{get numbers 1}}][{{get numbers 2}}]`, {
80+
numbers: [1, 2, 3],
81+
});
82+
83+
this.assertText('[1][2][3]');
84+
85+
this.runTask(() => this.rerender());
86+
87+
this.assertText('[1][2][3]');
88+
89+
this.runTask(() => set(this.context, 'numbers', [3, 2, 1]));
90+
91+
this.assertText('[3][2][1]');
92+
93+
this.runTask(() => set(this.context, 'numbers', [1, 2, 3]));
94+
95+
this.assertText('[1][2][3]');
96+
}
97+
98+
['@test should be able to get an object value with a path evaluating to a number']() {
99+
this.render(`{{#each indexes as |index|}}[{{get items index}}]{{/each}}`, {
100+
indexes: [1, 2, 3],
101+
items: {
102+
1: 'First',
103+
2: 'Second',
104+
3: 'Third'
105+
}
106+
});
107+
108+
this.assertText('[First][Second][Third]');
109+
110+
this.runTask(() => this.rerender());
111+
112+
this.assertText('[First][Second][Third]');
113+
114+
this.runTask(() => set(this.context, 'items.1', 'Qux'));
115+
116+
this.assertText('[Qux][Second][Third]');
117+
118+
this.runTask(() => set(this.context, 'items', { 1: 'First', 2: 'Second', 3: 'Third' }));
119+
120+
this.assertText('[First][Second][Third]');
121+
}
78122

79-
['@test should be able to get an array value with numeric keys']() {
123+
['@test should be able to get an array value with a path evaluating to a number']() {
80124
this.render(`{{#each numbers as |num index|}}[{{get numbers index}}]{{/each}}`, {
81125
numbers: [1, 2, 3],
82126
});

packages/ember-metal/lib/path_cache.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import Cache from './cache';
33
const firstDotIndexCache = new Cache(1000, key => key.indexOf('.'));
44

55
export function isPath(path) {
6-
return firstDotIndexCache.get(path) !== -1;
6+
return typeof path === 'string' && firstDotIndexCache.get(path) !== -1;
77
}

packages/ember-metal/lib/property_get.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ export function getPossibleMandatoryProxyValue(obj, keyName) {
7272
export function get(obj, keyName) {
7373
assert(`Get must be called with two arguments; an object and a property key`, arguments.length === 2);
7474
assert(`Cannot call get with '${keyName}' on an undefined object.`, obj !== undefined && obj !== null);
75-
assert(`The key provided to get must be a string, you passed ${keyName}`, typeof keyName === 'string');
76-
assert(`'this' in paths is not supported`, keyName.lastIndexOf('this.', 0) !== 0);
75+
assert(`The key provided to get must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || (typeof keyName === 'number' && !isNaN(keyName)));
76+
assert(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0);
7777
assert('Cannot call `get` with an empty string', keyName !== '');
7878

7979
let type = typeof obj;

packages/ember-metal/lib/property_set.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export function set(obj, keyName, value, tolerant) {
4545
arguments.length === 3 || arguments.length === 4
4646
);
4747
assert(`Cannot call set with '${keyName}' on an undefined object.`, obj && typeof obj === 'object' || typeof obj === 'function');
48-
assert(`The key provided to set must be a string, you passed ${keyName}`, typeof keyName === 'string');
49-
assert(`'this' in paths is not supported`, keyName.lastIndexOf('this.', 0) !== 0);
48+
assert(`The key provided to set must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || (typeof keyName === 'number' && !isNaN(keyName)));
49+
assert(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0);
5050
assert(`calling set on destroyed object: ${toString(obj)}.${keyName} = ${toString(value)}`, !obj.isDestroyed);
5151

5252
if (isPath(keyName)) {

packages/ember-metal/tests/accessors/get_test.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ moduleFor('get', class extends AbstractTestCase {
2727
}
2828
}
2929

30+
['@test should retrieve a number key on an object'](assert) {
31+
let obj = { 1: 'first' };
32+
33+
assert.equal(get(obj, 1), 'first');
34+
}
35+
36+
['@test should retrieve an array index'](assert) {
37+
let arr = ['first', 'second'];
38+
39+
assert.equal(get(arr, 0), 'first');
40+
assert.equal(get(arr, 1), 'second');
41+
}
42+
3043
['@test should not access a property more than once'](assert) {
3144
let count = 0;
3245
let obj = {
@@ -106,11 +119,10 @@ moduleFor('get', class extends AbstractTestCase {
106119

107120
['@test warn on attempts to use get with an unsupported property path']() {
108121
let obj = {};
109-
expectAssertion(() => get(obj, null), /The key provided to get must be a string, you passed null/);
110-
expectAssertion(() => get(obj, NaN), /The key provided to get must be a string, you passed NaN/);
111-
expectAssertion(() => get(obj, undefined), /The key provided to get must be a string, you passed undefined/);
112-
expectAssertion(() => get(obj, false), /The key provided to get must be a string, you passed false/);
113-
expectAssertion(() => get(obj, 42), /The key provided to get must be a string, you passed 42/);
122+
expectAssertion(() => get(obj, null), /The key provided to get must be a string or number, you passed null/);
123+
expectAssertion(() => get(obj, NaN), /The key provided to get must be a string or number, you passed NaN/);
124+
expectAssertion(() => get(obj, undefined), /The key provided to get must be a string or number, you passed undefined/);
125+
expectAssertion(() => get(obj, false), /The key provided to get must be a string or number, you passed false/);
114126
expectAssertion(() => get(obj, ''), /Cannot call `get` with an empty string/);
115127
}
116128

packages/ember-metal/tests/accessors/set_test.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ moduleFor('set', class extends AbstractTestCase {
3434
}
3535
}
3636

37+
['@test should set a number key on an object'](assert) {
38+
let obj = { };
39+
40+
set(obj, 1, 'first');
41+
assert.equal(obj[1], 'first');
42+
}
43+
44+
['@test should set an array index'](assert) {
45+
let arr = ['first', 'second'];
46+
47+
set(arr, 1, 'lol');
48+
assert.deepEqual(arr, ['first', 'lol']);
49+
}
50+
3751
['@test should call setUnknownProperty if defined and value is undefined'](assert) {
3852
let obj = {
3953
count: 0,
@@ -64,11 +78,10 @@ moduleFor('set', class extends AbstractTestCase {
6478

6579
['@test warn on attempts to use set with an unsupported property path']() {
6680
let obj = {};
67-
expectAssertion(() => set(obj, null, 42), /The key provided to set must be a string, you passed null/);
68-
expectAssertion(() => set(obj, NaN, 42), /The key provided to set must be a string, you passed NaN/);
69-
expectAssertion(() => set(obj, undefined, 42), /The key provided to set must be a string, you passed undefined/);
70-
expectAssertion(() => set(obj, false, 42), /The key provided to set must be a string, you passed false/);
71-
expectAssertion(() => set(obj, 42, 42), /The key provided to set must be a string, you passed 42/);
81+
expectAssertion(() => set(obj, null, 42), /The key provided to set must be a string or number, you passed null/);
82+
expectAssertion(() => set(obj, NaN, 42), /The key provided to set must be a string or number, you passed NaN/);
83+
expectAssertion(() => set(obj, undefined, 42), /The key provided to set must be a string or number, you passed undefined/);
84+
expectAssertion(() => set(obj, false, 42), /The key provided to set must be a string or number, you passed false/);
7285
}
7386

7487
['@test warn on attempts of calling set on a destroyed object']() {

0 commit comments

Comments
 (0)