123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- const fc = require("fast-check");
- const semver_1 = require("semver");
- const index_1 = require("./index");
- // Evaluate a string into JavaScript
- const evalValue = (str) => {
- // tslint:disable-next-line no-eval
- return eval(`(${str})`);
- };
- /**
- * Create a quick test function wrapper.
- */
- function test(value, result, indent, options) {
- return () => {
- expect(index_1.stringify(value, null, indent, options)).toEqual(result);
- };
- }
- /**
- * Create a wrapper for round-trip eval tests.
- */
- function testRoundTrip(expression, indent, options) {
- return () => test(evalValue(expression), expression, indent, options)();
- }
- /**
- * Check if syntax is supported.
- */
- function isSupported(expr) {
- try {
- // tslint:disable-next-line no-eval
- eval(expr);
- return true;
- }
- catch (err) {
- if (err.name === "SyntaxError")
- return false;
- throw err;
- }
- }
- /**
- * Generate a list of test cases to run.
- */
- function cases(cases) {
- return () => {
- for (const value of cases) {
- if (value)
- it(value, testRoundTrip(value));
- }
- };
- }
- /**
- * Conditionally execute test cases.
- */
- function describeIf(description, condition, fn) {
- return condition ? describe(description, fn) : describe.skip(description, fn);
- }
- describe("javascript-stringify", () => {
- describe("types", () => {
- describe("booleans", () => {
- it("should be stringified", test(true, "true"));
- });
- describe("strings", () => {
- it("should wrap in single quotes", test("string", "'string'"));
- it("should escape quote characters", test("'test'", "'\\'test\\''"));
- it("should escape control characters", test("multi\nline", "'multi\\nline'"));
- it("should escape back slashes", test("back\\slash", "'back\\\\slash'"));
- it("should escape certain unicode sequences", test("\u0602", "'\\u0602'"));
- });
- describe("numbers", () => {
- it("should stringify integers", test(10, "10"));
- it("should stringify floats", test(10.5, "10.5"));
- it('should stringify "NaN"', test(10.5, "10.5"));
- it('should stringify "Infinity"', test(Infinity, "Infinity"));
- it('should stringify "-Infinity"', test(-Infinity, "-Infinity"));
- it('should stringify "-0"', test(-0, "-0"));
- });
- describe("arrays", () => {
- it("should stringify as array shorthand", test([1, 2, 3], "[1,2,3]"));
- it("should indent elements", test([{ x: 10 }], "[\n\t{\n\t\tx: 10\n\t}\n]", "\t"));
- });
- describe("objects", () => {
- it("should stringify as object shorthand", test({ key: "value", "-": 10 }, "{key:'value','-':10}"));
- it("should stringify undefined keys", test({ a: true, b: undefined }, "{a:true,b:undefined}"));
- it("should stringify omit undefined keys", test({ a: true, b: undefined }, "{a:true}", null, {
- skipUndefinedProperties: true
- }));
- it("should quote reserved word keys", test({ if: true, else: false }, "{'if':true,'else':false}"));
- it("should not quote Object.prototype keys", test({ constructor: 1, toString: 2 }, "{constructor:1,toString:2}"));
- });
- describe("functions", () => {
- it("should reindent function bodies", test(evalValue(`function() {
- if (true) {
- return "hello";
- }
- }`), 'function () {\n if (true) {\n return "hello";\n }\n}', 2));
- it("should reindent function bodies in objects", test(evalValue(`
- {
- fn: function() {
- if (true) {
- return "hello";
- }
- }
- }
- `), '{\n fn: function () {\n if (true) {\n return "hello";\n }\n }\n}', 2));
- it("should reindent function bodies in arrays", test(evalValue(`[
- function() {
- if (true) {
- return "hello";
- }
- }
- ]`), '[\n function () {\n if (true) {\n return "hello";\n }\n }\n]', 2));
- it("should not need to reindent one-liners", testRoundTrip("{\n fn: function () { return; }\n}", 2));
- it("should gracefully handle unexpected Function.toString formats", () => {
- const origToString = Function.prototype.toString;
- Function.prototype.toString = () => "{nope}";
- try {
- expect(index_1.stringify(function () {
- /* Empty */
- })).toEqual("void '{nope}'");
- }
- finally {
- Function.prototype.toString = origToString;
- }
- });
- describe("omit the names of their keys", cases(["{name:function () {}}", "{'tricky name':function () {}}"]));
- });
- describe("native instances", () => {
- describe("Date", () => {
- const date = new Date();
- it("should stringify", test(date, "new Date(" + date.getTime() + ")"));
- });
- describe("RegExp", () => {
- it("should stringify as shorthand", test(/[abc]/gi, "/[abc]/gi"));
- });
- describe("Number", () => {
- it("should stringify", test(new Number(10), "new Number(10)"));
- });
- describe("String", () => {
- it("should stringify", test(new String("abc"), "new String('abc')"));
- });
- describe("Boolean", () => {
- it("should stringify", test(new Boolean(true), "new Boolean(true)"));
- });
- describeIf("Buffer", typeof Buffer === "function", () => {
- it("should stringify", test(Buffer.from("test"), "new Buffer('test')"));
- });
- describeIf("BigInt", typeof BigInt === "function", () => {
- it("should stringify", test(BigInt("10"), "BigInt('10')"));
- });
- describe("Error", () => {
- it("should stringify", test(new Error("test"), "new Error('test')"));
- });
- describe("unknown native type", () => {
- it("should be omitted", test({
- k: typeof process === "undefined"
- ? window.navigator
- : process
- }, "{}"));
- });
- });
- describeIf("ES6", typeof Array.from === "function", () => {
- describeIf("Map", typeof Map === "function", () => {
- it("should stringify", test(new Map([["key", "value"]]), "new Map([['key','value']])"));
- });
- describeIf("Set", typeof Set === "function", () => {
- it("should stringify", test(new Set(["key", "value"]), "new Set(['key','value'])"));
- });
- describe("arrow functions", () => {
- describe("should stringify", cases([
- "(a, b) => a + b",
- "o => { return o.a + o.b; }",
- "(a, b) => { if (a) { return b; } }",
- "(a, b) => ({ [a]: b })",
- "a => b => () => a + b"
- ]));
- it("should reindent function bodies", test(evalValue(" () => {\n" +
- " if (true) {\n" +
- ' return "hello";\n' +
- " }\n" +
- " }"), '() => {\n if (true) {\n return "hello";\n }\n}', 2));
- describeIf("arrows with patterns", isSupported("({x}) => x"), () => {
- describe("should stringify", cases([
- "({ x, y }) => x + y",
- "({ x, y }) => { if (x === '}') { return y; } }",
- "({ x, y = /[/})]/.test(x) }) => { return y ? x : 0; }"
- ]));
- });
- });
- describe("generators", () => {
- it("should stringify", testRoundTrip("function* (x) { yield x; }"));
- });
- describe("class notation", () => {
- it("should stringify classes", testRoundTrip("class {}"));
- it("should stringify class and method", testRoundTrip("class { method() {} }"));
- it("should stringify with newline", testRoundTrip("class\n{ method() {} }"));
- it("should stringify with comment", testRoundTrip("class/*test*/\n{ method() {} }"));
- });
- describe("method notation", () => {
- it("should stringify", testRoundTrip("{a(b, c) { return b + c; }}"));
- it("should stringify generator methods", testRoundTrip("{*a(b) { yield b; }}"));
- describe("should not be fooled by tricky names", cases([
- "{'function a'(b, c) { return b + c; }}",
- "{'a(a'(b, c) { return b + c; }}",
- "{'() => function '() {}}",
- "{'['() { return x[y]()\n{ return true; }}}",
- "{'() { return false;//'() { return true;\n}}"
- ]));
- it("should not be fooled by tricky generator names", testRoundTrip("{*'function a'(b, c) { return b + c; }}"));
- it("should not be fooled by empty names", testRoundTrip("{''(b, c) { return b + c; }}"));
- it("should not be fooled by keys that look like functions", () => {
- const fn = evalValue('{ "() => ": () => () => 42 }')["() => "];
- expect(index_1.stringify(fn)).toEqual("() => () => 42");
- });
- describe("should not be fooled by arrow functions", cases([
- "{a:(b, c) => b + c}",
- "{a:a => a + 1}",
- "{'() => ':() => () => 42}",
- '{\'() => "\':() => "() {//"}',
- '{\'() => "\':() => "() {`//"}',
- '{\'() => "\':() => "() {`${//"}',
- '{\'() => "\':() => "() {/*//"}',
- semver_1.satisfies(process.versions.node, "<=4 || >=10")
- ? "{'a => function ':a => function () { return a + 1; }}"
- : undefined
- ]));
- describe("should not be fooled by regexp literals", cases([
- "{' '(s) { return /}/.test(s); }}",
- "{' '(s) { return /abc/ .test(s); }}",
- "{' '() { return x / y; // /}\n}}",
- "{' '() { return / y; }//* } */}}",
- "{' '() { return delete / y; }/.x}}",
- "{' '() { switch (x) { case / y; }}/: }}}",
- "{' '() { if (x) return; else / y;}/; }}",
- "{' '() { return x in / y;}/; }}",
- "{' '() { return x instanceof / y;}/; }}",
- "{' '() { return new / y;}/.x; }}",
- "{' '() { throw / y;}/.x; }}",
- "{' '() { return typeof / y;}/; }}",
- "{' '() { void / y;}/; }}",
- "{' '() { return x, / y;}/; }}",
- "{' '() { return x; / y;}/; }}",
- "{' '() { return { x: / y;}/ }; }}",
- "{' '() { return x + / y;}/.x; }}",
- "{' '() { return x - / y;}/.x; }}",
- "{' '() { return !/ y;}/; }}",
- "{' '() { return ~/ y;}/.x; }}",
- "{' '() { return x && / y;}/; }}",
- "{' '() { return x || / y;}/; }}",
- "{' '() { return x ^ / y;}/.x; }}",
- "{' '() { return x * / y;}/.x; }}",
- "{' '() { return x / / y;}/.x; }}",
- "{' '() { return x % / y;}/.x; }}",
- "{' '() { return x < / y;}/.x; }}",
- "{' '() { return x > / y;}/.x; }}",
- "{' '() { return x <= / y;}/.x; }}",
- "{' '() { return x /= / y;}/.x; }}",
- "{' '() { return x ? / y;}/ : false; }}"
- ]));
- describe("should not be fooled by computed names", () => {
- it("1", test(evalValue('{ ["foobar".slice(3)](x) { return x + 1; } }'), "{bar(x) { return x + 1; }}"));
- it("2", test(evalValue('{[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\",\\"+s(b)+b)(JSON.stringify,",")]() {}")]() {}}'), '{\'[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\\\",\\\\"+s(b)+b)(JSON.stringify,",")]() {}")]() {}\'() {}}'));
- it("3", test(evalValue('{[`over${`6${"0".repeat(3)}`.replace("6", "9")}`]() { this.activateHair(); }}'), "{over9000() { this.activateHair(); }}"));
- it("4", test(evalValue("{[\"() {'\"]() {''}}"), "{'() {\\''() {''}}"));
- it("5", test(evalValue('{["() {`"]() {``}}'), "{'() {`'() {``}}"));
- it("6", test(evalValue('{["() {/*"]() {/*`${()=>{/*}*/}}'), "{'() {/*'() {/*`${()=>{/*}*/}}"));
- });
- // These two cases demonstrate that branching on
- // METHOD_NAMES_ARE_QUOTED is unavoidable--you can't write code
- // without it that will pass both of these cases on both node.js 4
- // and node.js 10. (If you think you can, consider that the name and
- // toString of the first case when executed on node.js 10 are
- // identical to the name and toString of the second case when
- // executed on node.js 4, so good luck telling them apart without
- // knowing which node you're on.)
- describe("should handle different versions of node correctly", () => {
- it("1", test(evalValue('{[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\",\\"+s(b)+b)(JSON.stringify,",")]() { return 0; /*")]() { return 0; /*() {/* */ return 1;}}'), '{\'[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\\\",\\\\"+s(b)+b)(JSON.stringify,",")]() { return 0; /*")]() { return 0; /*\'() { return 0; /*() {/* */ return 1;}}'));
- it("2", test(evalValue('{\'[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\\\",\\\\"+s(b)+b)(JSON.stringify,",")]() { return 0; /*")]() { return 0; /*\'() {/* */ return 1;}}'), '{\'[((s,a,b)=>a+s(a)+","+s(b)+b)(JSON.stringify,"[((s,a,b)=>a+s(a)+\\\\",\\\\"+s(b)+b)(JSON.stringify,",")]() { return 0; /*")]() { return 0; /*\'() {/* */ return 1;}}'));
- });
- it("should not be fooled by comments", test(evalValue("{'method' /* a comment! */ () /* another comment! */ {}}"), "{method() /* another comment! */ {}}"));
- it("should stringify extracted methods", () => {
- const fn = evalValue("{ foo(x) { return x + 1; } }").foo;
- expect(index_1.stringify(fn)).toEqual("function foo(x) { return x + 1; }");
- });
- it("should stringify extracted generators", () => {
- const fn = evalValue("{ *foo(x) { yield x; } }").foo;
- expect(index_1.stringify(fn)).toEqual("function* foo(x) { yield x; }");
- });
- it("should stringify extracted methods with tricky names", () => {
- const fn = evalValue('{ "a(a"(x) { return x + 1; } }')["a(a"];
- expect(index_1.stringify(fn)).toEqual("function (x) { return x + 1; }");
- });
- it("should stringify extracted methods with arrow-like tricky names", () => {
- const fn = evalValue('{ "() => function "(x) { return x + 1; } }')["() => function "];
- expect(index_1.stringify(fn)).toEqual("function (x) { return x + 1; }");
- });
- it("should stringify extracted methods with empty names", () => {
- const fn = evalValue('{ ""(x) { return x + 1; } }')[""];
- expect(index_1.stringify(fn)).toEqual("function (x) { return x + 1; }");
- });
- it("should handle transplanted names", () => {
- const fn = evalValue("{ foo(x) { return x + 1; } }").foo;
- expect(index_1.stringify({ bar: fn })).toEqual("{bar:function foo(x) { return x + 1; }}");
- });
- it("should handle transplanted names with generators", () => {
- const fn = evalValue("{ *foo(x) { yield x; } }").foo;
- expect(index_1.stringify({ bar: fn })).toEqual("{bar:function* foo(x) { yield x; }}");
- });
- it("should reindent methods", test(evalValue(" {\n" +
- " fn() {\n" +
- " if (true) {\n" +
- ' return "hello";\n' +
- " }\n" +
- " }\n" +
- " }"), '{\n fn() {\n if (true) {\n return "hello";\n }\n }\n}', 2));
- });
- });
- describe("ES2017", () => {
- describeIf("async functions", isSupported("(async function () {})"), () => {
- it("should stringify", testRoundTrip("async function (x) { await x; }"));
- it("should gracefully handle unexpected Function.toString formats", () => {
- const origToString = Function.prototype.toString;
- Function.prototype.toString = () => "{nope}";
- try {
- expect(index_1.stringify(evalValue("async function () {}"))).toEqual("void '{nope}'");
- }
- finally {
- Function.prototype.toString = origToString;
- }
- });
- });
- describeIf("async arrows", isSupported("async () => {}"), () => {
- describe("should stringify", cases([
- "async (x) => x + 1",
- "async x => x + 1",
- "async x => { await x.then(y => y + 1); }"
- ]));
- describe("should stringify as object properties", cases([
- "{f:async a => a + 1}",
- semver_1.satisfies(process.versions.node, "<=4 || >=10")
- ? "{'async a => function ':async a => function () { return a + 1; }}"
- : undefined
- ]));
- });
- });
- describe("ES2018", () => {
- describeIf("async generators", isSupported("(async function* () {})"), () => {
- it("should stringify", testRoundTrip("async function* (x) { yield x; }"));
- it("should gracefully handle unexpected Function.toString formats", () => {
- const origToString = Function.prototype.toString;
- Function.prototype.toString = () => "{nope}";
- try {
- expect(index_1.stringify(evalValue("async function* () {}"))).toEqual("void '{nope}'");
- }
- finally {
- Function.prototype.toString = origToString;
- }
- });
- });
- });
- describe("global", () => {
- it("should access the global in the current environment", testRoundTrip("Function('return this')()"));
- });
- });
- describe("circular references", () => {
- it("should omit circular references", () => {
- const obj = { key: "value" };
- obj.obj = obj;
- const result = index_1.stringify(obj);
- expect(result).toEqual("{key:'value'}");
- });
- it("should restore value", () => {
- const obj = { key: "value" };
- obj.obj = obj;
- const result = index_1.stringify(obj, null, null, { references: true });
- expect(result).toEqual("(function(){var x={key:'value'};x.obj=x;return x;}())");
- });
- it("should omit recursive array value", () => {
- const obj = [1, 2, 3];
- obj.push(obj);
- const result = index_1.stringify(obj);
- expect(result).toEqual("[1,2,3,undefined]");
- });
- it("should restore array value", () => {
- const obj = [1, 2, 3];
- obj.push(obj);
- const result = index_1.stringify(obj, null, null, { references: true });
- expect(result).toEqual("(function(){var x=[1,2,3,undefined];x[3]=x;return x;}())");
- });
- it("should print repeated values when no references enabled", () => {
- const obj = {};
- const child = {};
- obj.a = child;
- obj.b = child;
- const result = index_1.stringify(obj);
- expect(result).toEqual("{a:{},b:{}}");
- });
- it("should restore repeated values", () => {
- const obj = {};
- const child = {};
- obj.a = child;
- obj.b = child;
- const result = index_1.stringify(obj, null, null, { references: true });
- expect(result).toEqual("(function(){var x={a:{}};x.b=x.a;return x;}())");
- });
- it("should restore repeated values with indentation", function () {
- const obj = {};
- const child = {};
- obj.a = child;
- obj.b = child;
- const result = index_1.stringify(obj, null, 2, { references: true });
- expect(result).toEqual("(function () {\nvar x = {\n a: {}\n};\nx.b = x.a;\nreturn x;\n}())");
- });
- });
- describe("custom indent", () => {
- it("string", () => {
- const result = index_1.stringify({
- test: [1, 2, 3],
- nested: {
- key: "value"
- }
- }, null, "\t");
- expect(result).toEqual("{\n" +
- "\ttest: [\n\t\t1,\n\t\t2,\n\t\t3\n\t],\n" +
- "\tnested: {\n\t\tkey: 'value'\n\t}\n" +
- "}");
- });
- it("integer", () => {
- const result = index_1.stringify({
- test: [1, 2, 3],
- nested: {
- key: "value"
- }
- }, null, 2);
- expect(result).toEqual("{\n" +
- " test: [\n 1,\n 2,\n 3\n ],\n" +
- " nested: {\n key: 'value'\n }\n" +
- "}");
- });
- it("float", () => {
- const result = index_1.stringify({
- test: [1, 2, 3],
- nested: {
- key: "value"
- }
- }, null, 2.6);
- expect(result).toEqual("{\n" +
- " test: [\n 1,\n 2,\n 3\n ],\n" +
- " nested: {\n key: 'value'\n }\n" +
- "}");
- });
- });
- describe("replacer function", () => {
- it("should allow custom replacements", () => {
- let callCount = 0;
- const result = index_1.stringify({
- test: "value"
- }, function (value, indent, next) {
- callCount++;
- if (typeof value === "string") {
- return '"hello"';
- }
- return next(value);
- });
- expect(callCount).toEqual(2);
- expect(result).toEqual('{test:"hello"}');
- });
- it("change primitive to object", () => {
- const result = index_1.stringify({
- test: 10
- }, function (value, indent, next) {
- if (typeof value === "number") {
- return next({ obj: "value" });
- }
- return next(value);
- });
- expect(result).toEqual("{test:{obj:'value'}}");
- });
- it("change object to primitive", () => {
- const result = index_1.stringify({
- test: 10
- }, value => Object.prototype.toString.call(value));
- expect(result).toEqual("[object Object]");
- });
- it("should support object functions", () => {
- function makeRaw(str) {
- const fn = () => {
- /* Noop. */
- };
- fn.__expression = str;
- return fn;
- }
- const result = index_1.stringify({
- "no-console": makeRaw(`process.env.NODE_ENV === 'production' ? 'error' : 'off'`),
- "no-debugger": makeRaw(`process.env.NODE_ENV === 'production' ? 'error' : 'off'`)
- }, (val, indent, stringify) => {
- if (val && val.__expression) {
- return val.__expression;
- }
- return stringify(val);
- }, 2);
- expect(result).toEqual(`{
- 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
- 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
- }`);
- });
- });
- describe("max depth", () => {
- const obj = { a: { b: { c: 1 } } };
- it("should get all object", test(obj, "{a:{b:{c:1}}}"));
- it("should get part of the object", test(obj, "{a:{b:{}}}", null, { maxDepth: 2 }));
- it("should get part of the object when tracking references", test(obj, "{a:{b:{}}}", null, { maxDepth: 2, references: true }));
- });
- describe("property based", () => {
- it("should produce string evaluating to the original value", () => {
- fc.assert(fc.property(fc.anything(), value => {
- expect(evalValue(index_1.stringify(value))).toEqual(value);
- }));
- });
- });
- });
- //# sourceMappingURL=index.spec.js.map
|