-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathis-template-object.js
177 lines (166 loc) · 5.43 KB
/
is-template-object.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*---
info: |
Reflect.isTemplateObject returns true given a
template tag strings object and false otherwise.
es5id: TBD
description: Applies Reflect.isTemplateObject to various inputs.
--*/
// A template tag that applies the function under test
// and returns its result.
function directTag(strings) {
return Reflect.isTemplateObject(strings);
}
// A template tag that does the same but passes its
// argument via normal function application.
function indirectTag(strings) {
return directTag(strings);
}
// A template object that escapes the tag function body.
var escapedTemplateObject = null;
((x) => (escapedTemplateObject = x))`foo ${ null } bar`;
var foreignTemplateObject = null;
(() => {
const realm = $262.createRealm();
foreignTemplateObject =
(new realm.global.Function('return ((x) => x)`foreign strings`;'))();
})();
// Things that ought be recognized as template objects.
// Elements are [ description, candidate value ] pairs.
var posTestCases = [
[ 'direct', () => directTag`foo` ],
// It doesn't matter whether the strings were used with the tag that's running.
[ 'indirect', () => indirectTag`bar` ],
// Or whether there is even a tag function on the stack.
[ 'escaped', () => Reflect.isTemplateObject(escapedTemplateObject) ],
[
'called with null this',
() => Reflect.apply(Reflect.isTemplateObject, null, [ escapedTemplateObject ]),
],
// IsTemplateObject is realm-agnostic
[
'cross-realm template objects',
() => Reflect.isTemplateObject(foreignTemplateObject),
],
];
var falsePositives = [];
for (const [ message, f ] of posTestCases) {
let result = null;
try {
result = f();
} catch (e) {
falsePositives.push(message + ' threw');
continue;
}
if (result !== true) {
falsePositives.push(message);
}
}
// Things that should not be recognized as template objects.
// Elements are [ description, candidate value ] pairs.
var negTestCases = [
// Common values are not template string objects.
[ 'zero args', () => directTag() ],
[ 'null', () => directTag(null) ],
[ 'undefined', () => directTag(undefined) ],
[ 'zero', () => directTag(0) ],
[ '-zero', () => directTag(-0) ],
[ 'number', () => directTag(123) ],
[ 'NaN', () => directTag(NaN) ],
[ '+Inf', () => directTag(+Infinity) ],
[ '-Inf', () => directTag(-Infinity) ],
[ 'false', () => directTag(false) ],
[ 'true', () => directTag(true) ],
[ '{}', () => directTag({}) ],
[ '[ "x" ]', () => directTag([ "x" ]) ],
[ 'empty string', () => directTag('') ],
[ 'string', () => directTag('foo') ],
[ 'function', () => directTag(directTag) ],
// A proxy over a template string object is not a template string object.
[ 'proxy', () => directTag(new Proxy(escapedTemplateObject, {})) ],
[ 'Array.prototype', () => Reflect.isTemplateObject(Array.prototype) ],
// User code can't distinguish this case which is why this proposal adds value.
[
'forgery',
() => {
let arr = [ 'really really real' ];
Object.defineProperty(arr, 'raw', { value: arr });
Object.freeze(arr);
return directTag(arr);
}
],
// The implementation shouldn't muck with its argument.
[
'argument not poked', () => {
let poked = false;
// Use a proxy to see if isTemplateObject
// mucks with arg in an observable way.
let arg = new Proxy(
[],
// The proxy handler is itself a proxy which
// flips the poked bit if any proxy trap is
// invoked.
new Proxy(
{},
{
has(...args) {
poked = true;
return Reflect.has(...args);
},
get(...args) {
poked = true;
return Reflect.get(...args);
},
getPropertyDescriptor(...args) {
poked = true;
return Reflect.getPropertyDescriptor(...args);
},
getPrototypeOf(...args) {
poked = true;
return Reflect.getPrototypeOf(...args);
},
}));
return Reflect.isTemplateObject(arg) || poked;
}
],
// Since a motivating use case is to identify strings that
// originated within the current origin, the idiom from the spec note
// shouldn't return true for a template object that originated in a
// different realm.
[
'same-realm template object idiom',
() =>
Reflect.isTemplateObject(foreignTemplateObject)
&& foreignTemplateObject instanceof Array,
],
];
var falseNegatives = [];
for (const [ message, f ] of negTestCases) {
let result = null;
try {
result = f();
} catch (e) {
falseNegatives.push(message + ' threw');
continue;
}
if (result !== false) {
falseNegatives.push(message);
}
}
if (falsePositives.length) {
$ERROR(`#1: Reflect.isTemplateObject producing spurious positive results: ${ falsePositives }`);
}
if (falseNegatives.length) {
$ERROR(`#2: Reflect.isTemplateObject producing spurious negative results: ${ falseNegatives }`);
}
if (typeof Reflect.isTemplateObject !== 'function') {
$ERROR('#3: Reflect.isTemplateObject has wrong typeof');
}
if (Reflect.isTemplateObject.length !== 1) {
$ERROR('#4: Reflect.isTemplateObject has wrong length');
}
if (Reflect.isTemplateObject.name !== 'isTemplateObject') {
$ERROR('#5: Reflect.isTemplateObject has wrong name');
}
if (Object.prototype.toString.call(Reflect.isTemplateObject) !== '[object Function]') {
$ERROR('#6: Reflect.isTemplateObject is not a normal function');
}