Skip to content

Commit cf4c8bc

Browse files
author
Xotic750
committed
Fix Function.prototype.call and tests.
Ref: es-shims#304 Changed some code back as mentioned in comments. Changed more code as per comments Changed more code as per comments. Changed some variable names to better reflect comments. Fixed missed invocation. Added some comments and code changes as discussed. [tests] Remove unneeded jshint comment. [Tests] use the preferred it/skip pattern for this strict mode test. es-shims#345 (comment) And some other cleanup Added `arguments` expectations to tests. Add tests for `Object#toString` of typed arrays and Symbols, if they exist. Added note about typed array tests. Fix `hasToStringTagRegExpBug` Removed RegExp and Array bug detection as can not test, possible Opera 9. Fixed missing `force` on `defineProperties` that caused the patch to not be applied on IE<9. Fixed `Uint8ClampedArray` tests for Opera 11 and IE10 that don't have it. Removed offending test that was moved to detection, but forgotten. Avoid all possibilities of `call` calling `call`. Do not pass `undefined` argument, we know IE<9 has unfixable bug. Final cleanup (hopeully) Port over work from `apply` fix Move code so that it is specific to the fix.
1 parent 4b6dcad commit cf4c8bc

File tree

4 files changed

+246
-16
lines changed

4 files changed

+246
-16
lines changed

es5-shim.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var array_push = ArrayPrototype.push;
5656
var array_unshift = ArrayPrototype.unshift;
5757
var array_concat = ArrayPrototype.concat;
5858
var call = FunctionPrototype.call;
59+
var apply = FunctionPrototype.apply;
5960
var max = Math.max;
6061
var min = Math.min;
6162

@@ -163,7 +164,6 @@ var ES = {
163164
// http://es5.github.com/#x9.9
164165
/* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */
165166
ToObject: function (o) {
166-
/* jshint eqnull: true */
167167
if (o == null) { // this matches both null and undefined
168168
throw new TypeError("can't convert " + o + ' to object');
169169
}
@@ -321,14 +321,78 @@ defineProperties(FunctionPrototype, {
321321
});
322322

323323
// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
324-
// us it in defining shortcuts.
324+
// use it in defining shortcuts.
325325
var owns = call.bind(ObjectPrototype.hasOwnProperty);
326326
var toStr = call.bind(ObjectPrototype.toString);
327327
var strSlice = call.bind(StringPrototype.slice);
328328
var strSplit = call.bind(StringPrototype.split);
329329
var strIndexOf = call.bind(StringPrototype.indexOf);
330330
var push = call.bind(array_push);
331331

332+
// Tests for inconsistent or buggy `[[Class]]` strings.
333+
var hasToStringTagBasicBug = toStr() !== '[object Undefined]' || toStr(null) !== '[object Null]';
334+
var hasToStringTagLegacyArguments = toStr(arguments) !== '[object Arguments]';
335+
var hasToStringTagInconsistency = hasToStringTagBasicBug || hasToStringTagLegacyArguments;
336+
// Others that could be fixed:
337+
// Older ES3 native functions like `alert` return `[object Object]`.
338+
// Inconsistent `[[Class]]` strings for `window` or `global`.
339+
340+
if (hasToStringTagLegacyArguments) {
341+
// To prevent recursion if `Function#call` or `Function#apply` are patched.
342+
// Also robust, so can be used to replace standard '.call' amd `.apply`.
343+
call.apply = apply;
344+
call.call = call;
345+
apply.call = call;
346+
apply.apply = apply;
347+
348+
// This function is for use within `toStringTag` only.
349+
// To avoid any possibility of `call` recursion we use original `hasOwnProperty`.
350+
var isDuckTypeArguments = (function (hasOwnProperty) {
351+
return function (value) {
352+
return value != null && // Checks `null` or `undefined`.
353+
typeof value === 'object' &&
354+
call.call(hasOwnProperty, value, 'length') &&
355+
typeof value.length === 'number' &&
356+
value.length >= 0 &&
357+
!call.call(hasOwnProperty, value, 'arguments') &&
358+
call.call(hasOwnProperty, value, 'callee');
359+
// Using `isCallable` here is dangerous as it can cause recursion.
360+
// Unless modified to use `call.call`.
361+
// A typeof check would be ok if necessary.
362+
};
363+
}(ObjectPrototype.hasOwnProperty));
364+
365+
// Add whatever fixes for getting `[[Class]]` strings here.
366+
var toStringTag = function (value) {
367+
if (value === null) {
368+
return '[object Null]';
369+
}
370+
if (typeof value === 'undefined') {
371+
return '[object Undefined]';
372+
}
373+
if (hasToStringTagLegacyArguments && isDuckTypeArguments(value)) {
374+
return '[object Arguments]';
375+
}
376+
return call.call(to_string, value);
377+
};
378+
}
379+
380+
// ES-5 15.3.4.4
381+
// http://es5.github.io/#x15.3.4.4
382+
// The call() method calls a function with a given this value and arguments
383+
// provided individually.
384+
defineProperties(FunctionPrototype, {
385+
call: function (thisArg) {
386+
// If `this` is `Object#toString`.
387+
if (this === to_string) {
388+
return toStringTag(thisArg);
389+
}
390+
// All other calls.
391+
// `array_slice` is safe to use here, no known issue at present.
392+
return apply.call(this, thisArg, call.call(array_slice, arguments, 1));
393+
}
394+
}, hasToStringTagInconsistency);
395+
332396
//
333397
// Array
334398
// =====

tests/spec/s-array.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var hasStrictMode = (function () {
99

1010
return !this;
1111
}());
12+
var ifHasStrictIt = hasStrictMode ? it : xit;
1213

1314
describe('Array', function () {
1415
var testSubject;
@@ -123,17 +124,15 @@ describe('Array', function () {
123124
expect(toStr.call(listArg)).toBe('[object String]');
124125
});
125126

126-
if (hasStrictMode) {
127-
it('does not autobox the content in strict mode', function () {
128-
var context;
129-
[1].forEach(function () {
130-
'use strict';
127+
ifHasStrictIt('does not autobox the content in strict mode', function () {
128+
var context;
129+
[1].forEach(function () {
130+
'use strict';
131131

132-
context = this;
133-
}, 'x');
134-
expect(typeof context).toBe('string');
135-
});
136-
}
132+
context = this;
133+
}, 'x');
134+
expect(typeof context).toBe('string');
135+
});
137136
});
138137
describe('#some()', function () {
139138
var actual, expected, numberOfRuns;
@@ -1541,4 +1540,30 @@ describe('Array', function () {
15411540
expect(obj[2]).toBeUndefined();
15421541
});
15431542
});
1543+
1544+
describe('#slice()', function () {
1545+
it('works on arrays', function () {
1546+
var arr = [1, 2, 3, 4];
1547+
var result = arr.slice(1, 3);
1548+
expect(result).toEqual([2, 3]);
1549+
});
1550+
1551+
it('is generic', function () {
1552+
var obj = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4 };
1553+
var result = Array.prototype.slice.call(obj, 1, 3);
1554+
expect(result).toEqual([2, 3]);
1555+
1556+
obj = (function () {
1557+
return arguments;
1558+
}(1, 2, 3, 4));
1559+
result = Array.prototype.slice.call(obj, 1, 3);
1560+
expect(result).toEqual([2, 3]);
1561+
});
1562+
1563+
it('boxed string access', function () {
1564+
var obj = '1234';
1565+
var result = Array.prototype.slice.call(obj, 1, 3);
1566+
expect(result).toEqual(['2', '3']);
1567+
});
1568+
});
15441569
});

tests/spec/s-function.js

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,84 @@
1-
/* global describe, it, expect, beforeEach */
1+
/* global describe, it, xit, expect, beforeEach */
2+
var hasStrictMode = (function () {
3+
'use strict';
4+
5+
return !this;
6+
}());
7+
var ifHasStrictIt = hasStrictMode ? it : xit;
8+
var global = Function('return this')();
29

310
describe('Function', function () {
4-
'use strict';
11+
describe('#call()', function () {
12+
it('should pass correct arguments', function () {
13+
// https://github.com/es-shims/es5-shim/pull/345#discussion_r44878754
14+
var result;
15+
var testFn = function () {
16+
return Array.prototype.slice.call(arguments);
17+
};
18+
var argsExpected = [null, '1', 1, true, testFn];
19+
/* eslint-disable no-useless-call */
20+
result = testFn.call(undefined, null, '1', 1, true, testFn);
21+
expect(result).toEqual(argsExpected);
22+
result = testFn.call(null, null, '1', 1, true, testFn);
23+
expect(result).toEqual(argsExpected);
24+
/* eslint-enable no-useless-call */
25+
result = testFn.call('a', null, '1', 1, true, testFn);
26+
expect(result).toEqual(argsExpected);
27+
result = testFn.call(1, null, '1', 1, true, testFn);
28+
expect(result).toEqual(argsExpected);
29+
result = testFn.call(true, null, '1', 1, true, testFn);
30+
expect(result).toEqual(argsExpected);
31+
result = testFn.call(testFn, null, '1', 1, true, testFn);
32+
expect(result).toEqual(argsExpected);
33+
result = testFn.call(new Date(), null, '1', 1, true, testFn);
34+
expect(result).toEqual(argsExpected);
35+
});
36+
// https://github.com/es-shims/es5-shim/pull/345#discussion_r44878771
37+
ifHasStrictIt('should have correct context in strict mode', function () {
38+
'use strict';
39+
40+
var subject;
41+
var testFn = function () {
42+
return this;
43+
};
44+
expect(testFn.call()).toBe(undefined);
45+
/* eslint-disable no-useless-call */
46+
expect(testFn.call(undefined)).toBe(undefined);
47+
expect(testFn.call(null)).toBe(null);
48+
/* eslint-enable no-useless-call */
49+
expect(testFn.call('a')).toBe('a');
50+
expect(testFn.call(1)).toBe(1);
51+
expect(testFn.call(true)).toBe(true);
52+
expect(testFn.call(testFn)).toBe(testFn);
53+
subject = new Date();
54+
expect(testFn.call(subject)).toBe(subject);
55+
});
56+
it('should have correct context in non-strict mode', function () {
57+
var result;
58+
var subject;
59+
var testFn = function () {
60+
return this;
61+
};
62+
63+
expect(testFn.call()).toBe(global);
64+
/* eslint-disable no-useless-call */
65+
expect(testFn.call(undefined)).toBe(global);
66+
expect(testFn.call(null)).toBe(global);
67+
/* eslint-enable no-useless-call */
68+
result = testFn.call('a');
69+
expect(typeof result).toBe('object');
70+
expect(String(result)).toBe('a');
71+
result = testFn.call(1);
72+
expect(typeof result).toBe('object');
73+
expect(Number(result)).toBe(1);
74+
result = testFn.call(true);
75+
expect(typeof result).toBe('object');
76+
expect(Boolean(result)).toBe(true);
77+
expect(testFn.call(testFn)).toBe(testFn);
78+
subject = new Date();
79+
expect(testFn.call(subject)).toBe(subject);
80+
});
81+
});
582

683
describe('#apply()', function () {
784
it('works with arraylike objects', function () {

tests/spec/s-object.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/* global describe, it, xit, expect, beforeEach, jasmine, window */
1+
/* global describe, it, xit, expect, beforeEach, jasmine, window,
2+
ArrayBuffer, Float32Array, Float64Array, Int8Array, Int16Array,
3+
Int32Array, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array */
24

35
var ifWindowIt = typeof window === 'undefined' ? xit : it;
46
var extensionsPreventible = typeof Object.preventExtensions === 'function' && (function () {
@@ -22,7 +24,14 @@ var canFreeze = typeof Object.freeze === 'function' && (function () {
2224
return obj.a !== 3;
2325
}());
2426
var ifCanFreezeIt = canFreeze ? it : xit;
25-
27+
var toStr = Object.prototype.toString;
28+
var noop = function () {};
29+
var hasIteratorTag = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol';
30+
var ifHasIteratorTag = hasIteratorTag ? it : xit;
31+
var hasArrayBuffer = typeof ArrayBuffer === 'function';
32+
var ifHasArrayBuffer = hasArrayBuffer ? it : xit;
33+
var hasUint8ClampedArray = typeof Uint8ClampedArray === 'function';
34+
var ifHasUint8ClampedArray = hasUint8ClampedArray ? it : xit;
2635
describe('Object', function () {
2736
'use strict';
2837

@@ -356,4 +365,59 @@ describe('Object', function () {
356365
expect(obj instanceof Object).toBe(false);
357366
});
358367
});
368+
369+
describe('#toString', function () {
370+
it('basic', function () {
371+
expect(toStr.call()).toBe('[object Undefined]');
372+
/* eslint-disable no-useless-call */
373+
expect(toStr.call(undefined)).toBe('[object Undefined]');
374+
expect(toStr.call(null)).toBe('[object Null]');
375+
/* eslint-enable no-useless-call */
376+
expect(toStr.call(1)).toBe('[object Number]');
377+
expect(toStr.call(true)).toBe('[object Boolean]');
378+
expect(toStr.call('x')).toBe('[object String]');
379+
expect(toStr.call([1, 2, 3])).toBe('[object Array]');
380+
expect(toStr.call(arguments)).toBe('[object Arguments]');
381+
expect(toStr.call({})).toBe('[object Object]');
382+
expect(toStr.call(noop)).toBe('[object Function]');
383+
expect(toStr.call(new RegExp('c'))).toBe('[object RegExp]');
384+
expect(toStr.call(new Date())).toBe('[object Date]');
385+
expect(toStr.call(new Error('x'))).toBe('[object Error]');
386+
});
387+
ifHasArrayBuffer('Typed Arrays', function () {
388+
var buffer = new ArrayBuffer(8);
389+
expect(toStr.call(buffer)).toBe('[object ArrayBuffer]');
390+
expect(toStr.call(new Float32Array(buffer))).toBe('[object Float32Array]');
391+
expect(toStr.call(new Float64Array(buffer))).toBe('[object Float64Array]');
392+
expect(toStr.call(new Int8Array(buffer))).toBe('[object Int8Array]');
393+
expect(toStr.call(new Int16Array(buffer))).toBe('[object Int16Array]');
394+
expect(toStr.call(new Int32Array(buffer))).toBe('[object Int32Array]');
395+
expect(toStr.call(new Uint8Array(buffer))).toBe('[object Uint8Array]');
396+
expect(toStr.call(new Uint16Array(buffer))).toBe('[object Uint16Array]');
397+
expect(toStr.call(new Uint32Array(buffer))).toBe('[object Uint32Array]');
398+
});
399+
ifHasUint8ClampedArray('Uint8ClampedArray', function () {
400+
var buffer = new ArrayBuffer(8);
401+
expect(toStr.call(new Uint32Array(buffer))).toBe('[object Uint32Array]');
402+
});
403+
ifHasIteratorTag('Symbol.iterator', function () {
404+
expect(toStr.call(Symbol.iterator)).toBe('[object Symbol]');
405+
});
406+
// https://github.com/es-shims/es5-shim/pull/345#discussion_r44878834
407+
it('prototypes', function () {
408+
expect(toStr.call(Object.prototype)).toBe('[object Object]');
409+
expect(toStr.call(Array.prototype)).toBe('[object Array]');
410+
expect(toStr.call(Boolean.prototype)).toBe('[object Boolean]');
411+
expect(toStr.call(Function.prototype)).toBe('[object Function]');
412+
});
413+
// In ES6, many prototype objects stop being instances of themselves,
414+
// and instead would return '[object Object]'.
415+
xit('prototypes', function () {
416+
expect(toStr.call(Number.prototype)).toBe('[object Number]');
417+
expect(toStr.call(String.prototype)).toBe('[object String]');
418+
expect(toStr.call(Error.prototype)).toBe('[object Error]');
419+
expect(toStr.call(Date.prototype)).toBe('[object Date]');
420+
expect(toStr.call(RegExp.prototype)).toBe('[object RegExp]');
421+
});
422+
});
359423
});

0 commit comments

Comments
 (0)