ES6StringTemplate.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. // ES6 String Template tests -- verifies the API shape and basic functionality
  6. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  7. function ReturnString(str) {
  8. return str;
  9. }
  10. function GetCallsite(callsite) {
  11. return callsite;
  12. }
  13. function GetExpectedCachedCallsite() {
  14. return GetCallsite`some string template ${''} with replacements ${''}`;
  15. }
  16. function GetRawStringValue(callsite, idx) {
  17. return callsite.raw[idx];
  18. }
  19. function GetStringValue(callsite, idx) {
  20. return callsite[idx];
  21. }
  22. function tagReturnConstructor(callsite) {
  23. switch(callsite[0]) {
  24. case 'string':
  25. return String;
  26. case 'symbol':
  27. return Symbol;
  28. case 'array':
  29. return Array;
  30. case 'number':
  31. return Number;
  32. }
  33. return function() {
  34. return {
  35. name: 'constructed object'
  36. }
  37. };
  38. }
  39. function tagReturnConstructorWrapper() {
  40. return tagReturnConstructor;
  41. }
  42. var ObjectWithToString = {
  43. toString: function() { return "ObjectWithToString.toString() called!"; },
  44. value: 'ObjectWithToString.value (should not show up)'
  45. };
  46. var tests = [
  47. {
  48. name: "String constructor should have spec defined built-ins with correct lengths",
  49. body: function () {
  50. assert.isTrue(String.hasOwnProperty('raw'), "String constructor should have a raw method");
  51. assert.isTrue(String.raw.length === 1, "String.raw method takes 1 arguments");
  52. }
  53. },
  54. {
  55. name: "String.raw takes a callsite object and replacement values and returns a string that is the combination of the callsite raw strings and replacement values",
  56. body: function () {
  57. assert.throws(function () { String.raw.call(); }, TypeError, "String.raw throws TypeError if it is given no arguments", "String.raw: argument is not an Object");
  58. assert.throws(function () { String.raw.call('non-object'); }, TypeError, "String.raw throws TypeError if it is given non-object parameter", "String.raw: argument is not an Object");
  59. assert.throws(function () { String.raw.call({}); }, TypeError, "String.raw throws TypeError if it is given object which doesn't contain a raw property", "String.raw: argument is not an Object");
  60. assert.throws(function () { String.raw.call({ raw: undefined }); }, TypeError, "String.raw throws TypeError if it is given object which contains a raw property which is undefined", "String.raw: argument is not an Object");
  61. assert.throws(function () { String.raw.call({ raw: 'non-object' }); }, TypeError, "String.raw throws TypeError if it is given object which contains a raw property which is not an object", "String.raw: argument is not an Object");
  62. assert.areEqual("", String.raw``, "String.raw of empty string is the empty string");
  63. assert.areEqual("no replacement", String.raw`no replacement`, "no replacement expressions");
  64. assert.areEqual("\\u1234", String.raw`\u1234`, "unicode escape sequence");
  65. assert.areEqual("str\\ning1 simple replacement... \\r\\nstring end", String.raw`str\ning1 ${'simple replacement...'} \r\nstring end`, "string with replacement and escape sequences");
  66. assert.areEqual("str1\\\\ str2 str3\\\\ str4 str5\\\\ str6 str7\\\\", String.raw`str1\\ ${'str2'} str3\\ ${'str4'} str5\\ ${'str6'} str7\\`, "several replacements and escape sequences");
  67. assert.areEqual("begin (nested template here) end", String.raw`begin ${`(nested ${ReturnString('template')} here)`} end`, "nested string template literal");
  68. assert.areEqual("call toString... ObjectWithToString.toString() called! success...?", String.raw`call toString... ${ObjectWithToString} success...?`, "replacement expression is object with toString");
  69. assert.areEqual("simple raw string", String.raw({ raw: ["simple raw string"] }), "pass a custom object with raw property");
  70. assert.areEqual("string1 replacement string2", String.raw({ raw: ["string1 ", " string2"] }, ReturnString('replacement')), "pass a custom object with raw property and replacement parameter");
  71. assert.areEqual("string1 r1 string2", String.raw({ raw: ["string1 ", " string2"] }, 'r1', 'r2'), "pass object manually with more replacement parameters than expected");
  72. assert.areEqual("string1 string2", String.raw({ raw: ["string1 ", " string2"] }), "pass object manually with fewer replacement parameters than expected");
  73. assert.areEqual("string1 string2 string3", String.raw({ raw: ["string1 ", " string2 ", " string3"] }), "pass object manually with fewer replacement parameters than expected");
  74. assert.areEqual("", String.raw({ raw: [] }), "pass object manually containing with no raw strings");
  75. }
  76. },
  77. {
  78. name: "Callsite object passed to tag functions should have correct attributes",
  79. body: function() {
  80. var callsite = GetCallsite`simple template ${'with'} some ${'replacement'} expressions`;
  81. var descriptor;
  82. for (var i = 0; i < callsite.length; i++) {
  83. descriptor = Object.getOwnPropertyDescriptor(callsite, i);
  84. assert.isFalse(descriptor.writable, `callsite[${i}].descriptor.writable == false`);
  85. assert.isTrue(descriptor.enumerable, `callsite[${i}].descriptor.enumerable == true`);
  86. assert.isFalse(descriptor.configurable, `callsite[${i}].descriptor.configurable == false`);
  87. descriptor = Object.getOwnPropertyDescriptor(callsite.raw, i);
  88. assert.isFalse(descriptor.writable, `callsite.raw[${i}].descriptor.writable == false`);
  89. assert.isTrue(descriptor.enumerable, `callsite.raw[${i}].descriptor.enumerable == true`);
  90. assert.isFalse(descriptor.configurable, `callsite.raw[${i}].descriptor.configurable == false`);
  91. }
  92. descriptor = Object.getOwnPropertyDescriptor(callsite, 'raw');
  93. assert.isFalse(descriptor.writable, `callsite.raw.descriptor.writable == false`);
  94. assert.isFalse(descriptor.enumerable, `callsite.raw.descriptor.enumerable == false`);
  95. assert.isFalse(descriptor.configurable, `callsite.raw.descriptor.configurable == false`);
  96. assert.isTrue(Object.isFrozen(callsite), "callsite object is frozen");
  97. assert.isTrue(Object.isFrozen(callsite.raw), "callsite.raw object is frozen");
  98. }
  99. },
  100. {
  101. name: "Each string template literal corresponds to exactly one (cached) callsite object",
  102. body: function() {
  103. var callsite1 = GetCallsite`simple template literal 1`;
  104. var callsite2 = GetCallsite`simple template literal 2`;
  105. assert.isFalse(callsite1 === callsite2, "different string template literals create different callsite objects");
  106. var callsite3 = GetCallsite`simple template literal 3`;
  107. var callsite4 = GetCallsite`simple template literal 3`;
  108. assert.isTrue(callsite3 === callsite4, "different string template literals with the same string literal value create identical callsite objects");
  109. var loopCallsite = undefined;
  110. for (var i = 0; i < 10; i++) {
  111. var c = GetCallsite`loop template literal ${i}`;
  112. if (loopCallsite === undefined) {
  113. loopCallsite = c;
  114. } else {
  115. assert.isTrue(loopCallsite === c, "string template literal used in a loop reuses the same callsite object.");
  116. }
  117. assert.areEqual(2, c.length, "loop callsite has expected count of string literals");
  118. assert.areEqual("loop template literal ", c[0], "loop callsite has expected first string literal value");
  119. assert.areEqual("", c[1], "loop callsite has expected second string literal value");
  120. assert.areEqual(2, c.raw.length, "loop callsite.raw has expected count of string literals");
  121. assert.areEqual("loop template literal ", c.raw[0], "loop callsite.raw has expected first string literal value");
  122. assert.areEqual("", c.raw[1], "loop callsite.raw has expected second string literal value");
  123. }
  124. loopCallsite = undefined
  125. for (var i = 0; i < 10; i++) {
  126. var c = GetExpectedCachedCallsite();
  127. if (loopCallsite === undefined) {
  128. loopCallsite = c;
  129. } else {
  130. assert.isTrue(loopCallsite === c, "string template declared in other function returns same callsite object when function called.");
  131. }
  132. assert.areEqual(3, c.length, "loop callsite has expected count of string literals");
  133. assert.areEqual("some string template ", c[0], "loop callsite has expected first string literal value");
  134. assert.areEqual(" with replacements ", c[1], "loop callsite has expected second string literal value");
  135. assert.areEqual("", c[2], "loop callsite has expected third string literal value");
  136. assert.areEqual(3, c.raw.length, "loop callsite.raw has expected count of string literals");
  137. assert.areEqual("some string template ", c.raw[0], "loop callsite.raw has expected first string literal value");
  138. assert.areEqual(" with replacements ", c.raw[1], "loop callsite.raw has expected second string literal value");
  139. assert.areEqual("", c.raw[2], "loop callsite.raw has expected third string literal value");
  140. }
  141. }
  142. },
  143. {
  144. name: "BLUE: 490848 - string templates do not allow substitution expressions which contain '}' characters",
  145. body: function() {
  146. var foo;
  147. assert.areEqual("function () { return 'foo called'; }", `${foo = function() { return 'foo called'; }}`, "Function declaration (+assignment) in string template");
  148. assert.areEqual('function', typeof foo, "Assignment inside string template substitution expression");
  149. assert.areEqual('foo called', foo(), "Function declared in template expression can be called later");
  150. assert.areEqual("ObjectLiteral.toString() called", `${{toString:function(){return 'ObjectLiteral.toString() called';}}}`, "Object literal declared in string template expression");
  151. foo = undefined;
  152. assert.areEqual("foo: comma syntax", `${(foo = function() { return 'foo: comma syntax'; }, foo())}`, "Comma syntax in string template");
  153. assert.areEqual('function', typeof foo, "Assignment inside string template substitution expression");
  154. assert.areEqual('foo: comma syntax', foo(), "Function declared in template expression can be called later");
  155. assert.areEqual('{toString}', `{${{toString:function(){return 'toString';}}}}`, "Test all the permutations of '}' characters in a string template");
  156. }
  157. },
  158. {
  159. name: "BLUE: 525727 - using a let declaration inside a string template causes an assertion",
  160. body: function() {
  161. assert.throws(function () { eval('`${let x = 5}`;'); }, SyntaxError, "Let returns an identifier - can't be a substitution expression", "Expected '}'");
  162. assert.throws(function () { eval('`${a a}`;'); }, SyntaxError, "ParseExpr returns the first word as an identifier.", "Expected '}'");
  163. }
  164. },
  165. {
  166. name: "BLUE: 557210 - string templates do not check for escaped uses of substitution expressions",
  167. body: function() {
  168. // None of these should cause AV
  169. new Function("function z() {}; `z`;")();
  170. new Function("function z() {}; `${z}`;")();
  171. new Function("function z() {}; `${z}${z}`;")();
  172. new Function("function z() {}; `${z}${z}${z}`;")();
  173. new Function("function z() {}; `${'z'}${z}${z}`;")();
  174. new Function("function z() {}; `${'z'}${'z'}${z}`;")();
  175. new Function("function z() {}; '' + z + '';")();
  176. new Function("function z() {}; z`${`${z}`}`;")();
  177. new Function("function z() {}; z``;")();
  178. new Function("function z() {}; ``;")();
  179. new Function("(`${function(id) { return id }}`);")();
  180. new Function("function y() {} y`${`${'z'}${`${function(id) { return id }})${ /x/g >= 'c'}`}`}`;")();
  181. }
  182. },
  183. {
  184. name: "BLUE: 560073 - parsing a tagged string template does not respect the fInNew flag",
  185. body: function() {
  186. assert.throws(function() { new tagReturnConstructor`symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
  187. assert.isTrue(Array.isArray(new tagReturnConstructor`array`), "Simple case of calling tagged string template as constructor");
  188. assert.areEqual(6, new tagReturnConstructor`array${`string1`}${`string2`}`(6).length, "Calling tagged string template as constructor with replacements and arguments");
  189. assert.areEqual(`string`, new tagReturnConstructor`string`(`STRING`).toLowerCase(), "Create a string and pass parameters via String constructor returned from tag function");
  190. assert.throws(function() { new tagReturnConstructorWrapper`unused``symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
  191. assert.areEqual(new Number(0), new tagReturnConstructorWrapper`unused``number`, "Create a boxed number object via Number constructor returned from previous string template tag function");
  192. assert.areEqual(Object(`actual string value`), new tagReturnConstructorWrapper`unused``string`(`actual string value`), "Create a string via String constructor returned from previous string template tag function");
  193. }
  194. },
  195. {
  196. name: "String template tag function returning a constructor function should be constructable in a new expression",
  197. body: function() {
  198. function tagReturnNestedConstructorFunction(name) {
  199. return tagReturnConstructor([name]);
  200. }
  201. function tagReturnNestedConstructorObject(callsite) {
  202. return {
  203. constructor_array : [ Symbol, String, Array, Number ],
  204. constructor_tag_function : tagReturnConstructor,
  205. constructor_ordinary_function : tagReturnNestedConstructorFunction,
  206. constructor_object : { symbol : Symbol, string : String, array : Array, number : Number },
  207. constructor_nested : function() { return tagReturnNestedConstructorObject; }
  208. };
  209. }
  210. function tagReturnNestedConstructorArray() {
  211. return tagReturnNestedConstructorObject().constructor_array;
  212. }
  213. function tagReturnNestedConstructorArrayWrapper() {
  214. return [function() { return tagReturnNestedConstructorArray(); }];
  215. }
  216. assert.throws(function() { new tagReturnNestedConstructorObject`ignored`.constructor_array[0]; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
  217. assert.throws(function() { new tagReturnNestedConstructorObject`ignored`.constructor_tag_function`symbol`; }, TypeError, "Calling 'new Symbol()' throws so we should see a throw if we try to new the return from the tag function if it is Symbol", "Function is not a constructor");
  218. assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`.constructor_ordinary_function(`symbol`), "Mixing MemberExpression token types");
  219. assert.throws(function() { new new tagReturnNestedConstructorObject`ignored`.constructor_ordinary_function(`symbol`); }, TypeError, "new constructor_ordinary_function returns Symbol which should throw if new'd", "Function is not a constructor");
  220. assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.symbol, `Mixing MemberExpression token types`);
  221. assert.isTrue(Array.isArray(new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.array), `Mixing MemberExpression token types`);
  222. assert.areEqual(`object`, typeof new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.number(42), `Mixing MemberExpression token types`);
  223. assert.areEqual(`real string`, new new tagReturnNestedConstructorObject`ignored`.constructor_nested(`also ignored`)`one more ignored thing`.constructor_object.string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
  224. assert.isTrue(Symbol === new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.symbol, `Mixing MemberExpression token types`);
  225. assert.isTrue(Array.isArray(new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.array), `Mixing MemberExpression token types`);
  226. assert.areEqual(`object`, typeof new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.number(42), `Mixing MemberExpression token types`);
  227. assert.areEqual(`real string`, new new tagReturnNestedConstructorObject`ignored`[`constructor_nested`](`also ignored`)`one more ignored thing`.constructor_object.string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
  228. assert.isTrue(Symbol === tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].symbol, `Mixing MemberExpression token types`);
  229. assert.isTrue(Array.isArray(new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].array), `Mixing MemberExpression token types`);
  230. assert.areEqual(`object`, typeof new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].number(42), `Mixing MemberExpression token types`);
  231. assert.areEqual(`real string`, new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`].string(`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
  232. assert.isTrue(Symbol === tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`symbol`], `Mixing MemberExpression token types`);
  233. assert.isTrue(Array.isArray(new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`array`]), `Mixing MemberExpression token types`);
  234. assert.areEqual(`object`, typeof new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`number`](42), `Mixing MemberExpression token types`);
  235. assert.areEqual(`real string`, new tagReturnNestedConstructorObject`ignored`[`constructor_nested`]`also ignored``one more ignored thing`[`constructor_object`][`string`](`Real String`).toLowerCase(), `Mixing MemberExpression token types`);
  236. assert.throws(function() { new tagReturnNestedConstructorArray`ignored`[0]; }, TypeError, `Array access returns Symbol which throws when called as new expression`, `Function is not a constructor`);
  237. assert.isTrue(Array.isArray(new tagReturnNestedConstructorArray`ignored`[2]), `Array access returning Array used in a new expression`);
  238. assert.areEqual(Object(42), new tagReturnNestedConstructorArray`ignored`[3](42), `Array access returning Number used in a new expression`);
  239. assert.isTrue(Array.isArray(new tagReturnNestedConstructorArrayWrapper`ignored`[0]`also ignored`[2]), `Array access returning Array used in a new expression`);
  240. assert.areEqual(`string`, new tagReturnNestedConstructorArrayWrapper`ignored`[0]`also ignored`[1](`STRING`).toLowerCase(), `Array access returning String used in a new expression`);
  241. assert.areEqual(`string objectstring literal,,,`, String.raw`${new tagReturnNestedConstructorArray`ignored${String.raw`nothing`}`[1](`string object`)}string literal${new tagReturnNestedConstructorObject`ignored`[`constructor_object`][`array`](4)}`, `Nested string templates can have their own new expressions`);
  242. function tagReturnNestedConstructorObjectWrapper(callsite, expr1, expr2) {
  243. assert.areEqual([`string literal (pre)`,`string literal (middle)`,`string literal (post)`], callsite, `Callsite cooked string constants are correct`);
  244. assert.areEqual([`string literal (pre)`,`string literal (middle)`,`string literal (post)`], callsite.raw, `Callsite raw string constants are correct`);
  245. assert.areEqual(Object(`s1bprereplacement1s1bpost`), expr1, `First replacement expression is created via 'new String("...")'`);
  246. assert.isTrue(Array.isArray(expr2), `Second replacement expression is created via Array constructor`)
  247. assert.areEqual(new Array(6), expr2, `Second replacement expression has the right size and contents`);
  248. return function() { return tagReturnNestedConstructorObject(); }
  249. }
  250. assert.isTrue(Array.isArray(
  251. new tagReturnNestedConstructorObjectWrapper
  252. `string literal (pre)${new tagReturnNestedConstructorArray`ignored${String.raw`ignored replacement`}ignored`[1](`s1bpre${String.raw`replacement1`}s1bpost`)}string literal (middle)${new tagReturnNestedConstructorObject`s2pre${new Number(42)}s2post`[`constructor_object`][`array`](6)}string literal (post)``ignored`
  253. [`constructor_object`][`array`]
  254. ),
  255. `Many-levels of nested string templates can all have different expression types - NewExpressions, MemberExpressions, PrimaryExpressions`
  256. )
  257. var _counter = 0;
  258. function nestedNewOperatorFunction(callsite) {
  259. if (_counter > 2) {
  260. return tagReturnNestedConstructorFunction(callsite ? callsite[0] : ``);
  261. } else {
  262. _counter++;
  263. return nestedNewOperatorFunction;
  264. }
  265. }
  266. var obj = new new new new new nestedNewOperatorFunction
  267. assert.areEqual(`constructed object`, obj.name, `Calling nested constructor helper with no arguments eventually returns a constructor which returns an object with name property`);
  268. _counter = 0;
  269. assert.areEqual(new String(`help...`), new new new new new nestedNewOperatorFunction(`1`)(`2`)(`3`)([`string`])(`help...`), `Nested constructor wrapper chooses String builtin constructor`)
  270. _counter = 0;
  271. assert.isTrue(Array.isArray(new nestedNewOperatorFunction`1``2``3``array`), `Nested constructor wrapper is called inherently by string template evaluation - empty arguments`)
  272. _counter = 0;
  273. assert.areEqual(new String(`help...`), new nestedNewOperatorFunction`1``2``3``string`(`help...`), `Nested constructor wrapper is called inherently by string template evaluation - passing arguments to NewExpression`)
  274. }
  275. },
  276. {
  277. name: "Multiple post-fix operators in a row including string templates",
  278. body: function() {
  279. function foo() {
  280. return { 'something' : function() { return ['v1','v2','v3']; } };
  281. }
  282. assert.areEqual('v3', foo`'nothing'`['something']()[2], "Multiple post-fix operators after a string template");
  283. function tag(callsite, replacement) {
  284. return {
  285. v : callsite[0] + " " + replacement,
  286. f : function() { return ['a1', function(s) { return s; }]; }
  287. };
  288. }
  289. assert.areEqual('another string template - ', tag`a ${'b'} c`.f()[1]`another string template - ${'with replacements'}`[0], "Multiple post-fix operators including multiple string templates");
  290. }
  291. },
  292. {
  293. name: "String template callsite objects are unique per-Realm and indexed by the raw strings",
  294. body: function() {
  295. var callsite = undefined;
  296. var counter = 0;
  297. function tag(c)
  298. {
  299. counter++;
  300. assert.areEqual('uniquestringforrealmcachetest\\n', c.raw[0], 'String template callsite has correct raw value');
  301. if (callsite === undefined) {
  302. callsite = c;
  303. } else {
  304. assert.isTrue(c === callsite, 'Callsite is correctly cached per-Realm');
  305. }
  306. }
  307. function foo() {
  308. tag`uniquestringforrealmcachetest\n`;
  309. tag`uniquestringforrealmcachetest\n`;
  310. }
  311. foo();
  312. foo();
  313. function foo2() {
  314. tag`uniquestringforrealmcachetest\n`;
  315. tag`uniquestringforrealmcachetest\n`;
  316. }
  317. foo2();
  318. foo2();
  319. function foo3() {
  320. eval('tag`uniquestringforrealmcachetest\\n`');
  321. eval('tag`uniquestringforrealmcachetest\\n`');
  322. }
  323. foo3();
  324. foo3();
  325. counter = 0;
  326. var foo4 = new Function('t','t`uniquestringforrealmcachetest\\n`;');
  327. foo4(tag);
  328. foo4(tag);
  329. assert.areEqual(2, counter, "tag function is called correct number of times");
  330. counter = 0;
  331. var foo5 = new Function('t','eval("t`uniquestringforrealmcachetest\\\\n`;");');
  332. foo5(tag);
  333. foo5(tag);
  334. assert.areEqual(2, counter, "tag function is called correct number of times");
  335. }
  336. },
  337. {
  338. name: "Callsite objects are not strict equal if raw strings differ (even when cooked strings are strict equal)",
  339. body: function() {
  340. var callsite1 = GetCallsite`before
  341. after`;
  342. var callsite2 = GetCallsite`before\nafter`;
  343. assert.areEqual("before\nafter", callsite1[0], 'Explicit line terminator character is copied directly into cooked strings');
  344. assert.areEqual("before\nafter", callsite2[0], 'Escaped line terminator is translated into cooked strings');
  345. assert.areEqual("before\nafter", callsite1.raw[0], 'Explicit line terminator character is copied directly into raw strings');
  346. assert.areEqual("before\\nafter", callsite2.raw[0], 'Escaped line terminator is re-escaped into raw strings');
  347. assert.isTrue(callsite1[0] === callsite2[0], 'Cooked strings are strictly equal');
  348. assert.isFalse(callsite1.raw[0] === callsite2.raw[0], 'Raw strings are not strictly equal');
  349. assert.isFalse(callsite1 === callsite2, 'Callsite objects are not the same ');
  350. }
  351. },
  352. {
  353. name: "Callsite objects are constant even if replacement values differ",
  354. body: function() {
  355. var callsite1 = GetCallsite`string1${'r1'}string2${'r2'}string3`;
  356. var callsite2 = GetCallsite`string1${'r3'}string2${'r4'}string3`;
  357. assert.isTrue(callsite1 === callsite2, "Callsite objects are strictly equal");
  358. }
  359. },
  360. {
  361. name: "Octal escape sequences are not allowed in string template literals",
  362. body: function() {
  363. assert.throws(function () { eval('print(`\\00`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  364. assert.throws(function () { eval('print(`\\01`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  365. assert.throws(function () { eval('print(`\\1`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  366. assert.throws(function () { eval('print(`\\2`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  367. assert.throws(function () { eval('print(`\\3`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  368. assert.throws(function () { eval('print(`\\4`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  369. assert.throws(function () { eval('print(`\\5`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  370. assert.throws(function () { eval('print(`\\6`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  371. assert.throws(function () { eval('print(`\\7`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  372. assert.throws(function () { eval('print(`\\10`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  373. assert.throws(function () { eval('print(`\\50`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  374. assert.throws(function () { eval('print(`\\30`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  375. assert.throws(function () { eval('print(`\\70`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  376. assert.throws(function () { eval('print(`\\123`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  377. assert.throws(function () { eval('print(`\\377`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  378. assert.throws(function () { eval('print(`\\0123`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  379. assert.throws(function () { eval('print(`\\567`)'); }, SyntaxError, "Scanning an octal escape sequence throws SyntaxError.", "Octal numeric literals and escape characters not allowed in strict mode");
  380. }
  381. },
  382. {
  383. name: "Extended unicode escape sequences",
  384. body: function() {
  385. assert.areEqual("a\u{d}c", `a\u{d}c`, "Cooked value for extended unicode escape sequence of 1 hex character");
  386. assert.areEqual("a\\u{d}c", GetRawStringValue`a\u{d}c${0}`, "Raw value for extended unicode escape sequence of 1 hex character");
  387. assert.areEqual("a\u{62}c", `a\u{62}c`, "Cooked value for extended unicode escape sequence of 2 hex characters");
  388. assert.areEqual("a\\u{62}c", GetRawStringValue`a\u{62}c${0}`, "Raw value for extended unicode escape sequence of 2 hex characters");
  389. assert.areEqual("a\u{062}c", `a\u{062}c`, "Cooked value for extended unicode escape sequence of 3 hex characters");
  390. assert.areEqual("a\\u{062}c", GetRawStringValue`a\u{062}c${0}`, "Raw value for extended unicode escape sequence of 3 hex characters");
  391. assert.areEqual("a\u{0062}c", `a\u{0062}c`, "Cooked value for extended unicode escape sequence of 4 hex characters");
  392. assert.areEqual("a\\u{0062}c", GetRawStringValue`a\u{0062}c${0}`, "Raw value for extended unicode escape sequence of 4 hex characters");
  393. assert.areEqual("a\u{00062}c", `a\u{00062}c`, "Cooked value for extended unicode escape sequence of 5 hex characters");
  394. assert.areEqual("a\\u{00062}c", GetRawStringValue`a\u{00062}c${0}`, "Raw value for extended unicode escape sequence of 5 hex characters");
  395. assert.areEqual("a\u{000062}c", `a\u{000062}c`, "Cooked value for extended unicode escape sequence of 6 hex characters");
  396. assert.areEqual("a\\u{000062}c", GetRawStringValue`a\u{000062}c${0}`, "Raw value for extended unicode escape sequence of 6 hex characters");
  397. assert.areEqual("a\u{00000062}c", `a\u{00000062}c`, "Cooked value for extended unicode escape sequence of 7 hex characters");
  398. assert.areEqual("a\\u{00000062}c", GetRawStringValue`a\u{00000062}c${0}`, "Raw value for extended unicode escape sequence of 7 hex characters");
  399. assert.areEqual("a\u{000000062}c", `a\u{000000062}c`, "Cooked value for extended unicode escape sequence of 8 hex characters");
  400. assert.areEqual("a\\u{000000062}c", GetRawStringValue`a\u{000000062}c${0}`, "Raw value for extended unicode escape sequence of 8 hex characters");
  401. }
  402. },
  403. {
  404. name: "Template value for escaped null characters",
  405. body: function() {
  406. assert.areEqual("\0", eval("GetRawStringValue`\0${0}`"), "The template raw value of <NULL> is <NULL>");
  407. assert.areEqual("\\\u0030", eval("GetRawStringValue`\\\0${0}`"), "The template raw value of \\<NULL> is \\ + \u0030");
  408. assert.areEqual("\0", eval("GetStringValue`\0${0}`"), "The template cooked value of <NULL> is <NULL>");
  409. assert.areEqual("\0", eval("GetStringValue`\\\0${0}`"), "The template cooked value of \\<NULL> is <NULL>");
  410. }
  411. },
  412. {
  413. name: "String template line terminator sequence normalization",
  414. body: function() {
  415. assert.isTrue(eval("GetStringValue`\n${0}`") === "\n", "String template literal doesn't normalize <LF> line terminator sequence"); // `<LF>` => '<LF>'
  416. assert.isTrue(eval("GetStringValue`\\\n${0}`") === "", "String template literal ignores escaped <LF> line continuation token"); // `\<LF>` => ''
  417. assert.isTrue(eval("GetStringValue`\\n${0}`") === "\n", "String template literal doesn't normalize <LF> escape sequence"); // `\n` => '<LF>'
  418. assert.isTrue(eval("GetStringValue`\r${0}`") === "\n", "String template literal normalizes <CR> line terminator sequence to <LF>"); // `<CR>` => '<LF>'
  419. assert.isTrue(eval("GetStringValue`\\\r${0}`") === "", "String template literal ignores escaped <CR> line continuation token"); // `\<CR>` => ''
  420. assert.isTrue(eval("GetStringValue`\\r${0}`") === "\r", "String template literal doesn't normalize <CR> escape sequence"); // `\r` => '<CR>'
  421. assert.isTrue(eval("GetStringValue`\r\n${0}`") === "\n", "String template literal normalizes <CR><LF> line terminator sequence to <LF>"); // `<CR><LF>` => '<LF>'
  422. assert.isTrue(eval("GetStringValue`\\\r\\\n${0}`") === "", "String template literal ignores separately escaped <CR><LF> line continuation sequence tokens"); // `\<CR>\<LF>` => ''
  423. assert.isTrue(eval("GetStringValue`\\\r\n${0}`") === "", "String template literal ignores escaped <CR><LF> line continuation sequence"); // `\<CR><LF>` => ''
  424. assert.isTrue(eval("GetStringValue`\r\\\n${0}`") === "\n", "String template literal normalizes <CR> line terminator sequence if the next character is <LF> line continuation sequence"); // `<CR>\<LF>` => '<LF>'
  425. assert.isTrue(eval("GetStringValue`\\r\\n${0}`") === "\r\n", "String template literal doesn't normalize <CR><LF> as escape sequences"); // `\r\n` => '<CR><LF>'
  426. assert.isTrue(eval("GetStringValue`\\r\n${0}`") === "\r\n", "String template literal doesn't normalize <CR> as an escape sequence if the next character is <LF> line terminator sequence"); // `\r<LF>` => '<CR><LF>'
  427. assert.isTrue(eval("GetStringValue`\r\\n${0}`") === "\n\n", "String template literal normalizes <CR> as a line terminator sequence if the next character is <LF> escape sequence"); // `<CR>\n` => '<LF><LF>'
  428. assert.isTrue(eval("GetStringValue`\\\r\\n${0}`") === "\n", "String template literal ignores <CR> as a line continuation if the next character is <LF> escape sequence"); // `\<CR>\n` => '<LF>'
  429. assert.isTrue(eval("GetStringValue`\\r\\\n${0}`") === "\r", "String template literal doesn't normalize <CR> as an escape sequence if the next character is <LF> line continuation token"); // `\r\<LF>` => '<CR>'
  430. assert.isTrue(eval("GetStringValue`\u2028${0}`") === "\u2028", "String template literal doesn't normalize <LS> line terminator sequence"); // `<LS>` => '<LS>'
  431. assert.isTrue(eval("GetStringValue`\\\u2028${0}`") === "", "String template literal ignores escaped <LS> line continuation token"); // `\<LS>` => ''
  432. assert.isTrue(eval("GetStringValue`\u2029${0}`") === "\u2029", "String template literal doesn't normalize <PS> line terminator sequence"); // `<PS>` => '<PS>'
  433. assert.isTrue(eval("GetStringValue`\\\u2029${0}`") === "", "String template literal ignores escaped <PS> line continuation token"); // `\<PS>` => ''
  434. }
  435. },
  436. {
  437. name: "String template line terminator sequence normalization in raw string values",
  438. body: function() {
  439. assert.isTrue(eval("GetRawStringValue`\n${0}`") === '\n', "String template raw literal doesn't normalize <LF> line terminator sequence"); // `<LF>`.raw => '<LF>'
  440. assert.isTrue(eval("GetRawStringValue`\\\n${0}`") === "\\\n", "String template raw literal doesn't normalize escaped <LF> line continuation token"); // `\<LF>`.raw => '\<LF>'
  441. assert.isTrue(eval("GetRawStringValue`\\n${0}`") === "\\n", "String template raw literal doesn't evaluate <LF> escape sequence"); // `\n`.raw => '\n'
  442. assert.isTrue(eval("GetRawStringValue`\r${0}`") === "\n", "String template raw literal normalizes <CR> line terminator sequence to <LF>"); // `<CR>`.raw => '<LF>'
  443. assert.isTrue(eval("GetRawStringValue`\\\r${0}`") === "\\\n", "String template raw literal normalizes escaped <CR> line continuation token to <LF>"); // `\<CR>`.raw => '\<LF>'
  444. assert.isTrue(eval("GetRawStringValue`\\r${0}`") === "\\r", "String template raw literal doesn't evaluate <CR> escape sequence"); // `\r`.raw => '\r'
  445. assert.isTrue(eval("GetRawStringValue`\r\n${0}`") === "\n", "String template raw literal normalizes <CR><LF> line terminator sequence to <LF>"); // `<CR><LF>`.raw => '<LF>'
  446. assert.isTrue(eval("GetRawStringValue`\\\r\\\n${0}`") === "\\\n\\\n", "String template raw literal normalizes separately escaped <CR><LF> line continuation sequence tokens"); // `\<CR>\<LF>`.raw => '\<LF>\<LF>'
  447. assert.isTrue(eval("GetRawStringValue`\\\r\n${0}`") === "\\\n", "String template raw literal normalizes escaped <CR><LF> line continuation sequence to <LF>"); // `\<CR><LF>`.raw => '\<LF>'
  448. assert.isTrue(eval("GetRawStringValue`\r\\\n${0}`") === "\n\\\n", "String template raw literal normalizes <CR> line terminator sequence if the next character is <LF> line continuation sequence"); // `<CR>\<LF>`.raw => '<LF>\<LF>'
  449. assert.isTrue(eval("GetRawStringValue`\\r\\n${0}`") === "\\r\\n", "String template raw literal doesn't evaluate <CR><LF> as escape sequences"); // `\r\n`.raw => '\r\n'
  450. assert.isTrue(eval("GetRawStringValue`\\r\n${0}`") === "\\r\n", "String template raw literal doesn't evaluate <CR> as an escape sequence if the next character is <LF> line terminator sequence"); // `\r<LF>`.raw => '\r<LF>'
  451. assert.isTrue(eval("GetRawStringValue`\r\\n${0}`") === "\n\\n", "String template raw literal normalizes <CR> as a line terminator sequence if the next character is <LF> escape sequence"); // `<CR>\n`.raw => '<LF>\n'
  452. assert.isTrue(eval("GetRawStringValue`\\\r\\n${0}`") === "\\\n\\n", "String template raw literal normalizes <CR> as a line continuation if the next character is <LF> escape sequence"); // `\<CR>\n`.raw => '\<LF>\n'
  453. assert.isTrue(eval("GetRawStringValue`\\r\\\n${0}`") === "\\r\\\n", "String template raw literal doesn't evaluate <CR> as an escape sequence if the next character is <LF> line continuation token"); // `\r\<LF>`.raw => '\r\<LF>'
  454. assert.isTrue(eval("GetRawStringValue`\u2028${0}`") === "\u2028", "String template raw literal doesn't normalize <LS> line terminator sequence"); // `<LS>`.raw => '<LS>'
  455. assert.isTrue(eval("GetRawStringValue`\\\u2028${0}`") === "\\\u2028", "String template raw literal doesn't normalize escaped <LS> line continuation token"); // `\<LS>`.raw => '\<LS>'
  456. assert.isTrue(eval("GetRawStringValue`\u2029${0}`") === "\u2029", "String template raw literal doesn't normalize <PS> line terminator sequence"); // `<PS>`.raw => '<PS>'
  457. assert.isTrue(eval("GetRawStringValue`\\\u2029${0}`") === "\\\u2029", "String template raw literal doesn't normalize escaped <PS> line continuation token"); // `\<PS>`.raw => '\<PS>'
  458. }
  459. },
  460. {
  461. name: "Bug fix : 4532336 String Template should trigger ToString on the substitution expression",
  462. body: function() {
  463. var a = {
  464. toString: function (){ return "foo";},
  465. valueOf: function() { return "bar";}
  466. };
  467. assert.areEqual(`${a}`, "foo", "toString should be called instead of valueOf on the substitution expression");
  468. }
  469. },
  470. {
  471. name: "String template converts `\\0` into a null character - not an octal escape sequence",
  472. body: function() {
  473. assert.areEqual('\0', `\0`, "Simple null escape sequence in string template is treated the same as in a normal string");
  474. assert.areEqual('\08', `\08`, "Null escape sequence followed by non-octal number is valid");
  475. assert.areEqual('\0abc', `\0abc`, "Null escape sequence followed by non-number is valid");
  476. assert.areEqual('\0\0', `\0\0`, "Null escape sequence followed by another null escape sequence is valid");
  477. var called = false;
  478. function tag(obj) {
  479. assert.areEqual('\0', obj[0], "Null escape sequence is cooked into null character");
  480. assert.areEqual('\\0', obj.raw[0], "Null escape sequence is not cooked in raw string");
  481. called = true;
  482. }
  483. tag`\0`;
  484. assert.isTrue(called);
  485. }
  486. },
  487. ];
  488. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });