diff --git a/src/dataToEsm.ts b/src/dataToEsm.ts index 710f173..01cd4a3 100644 --- a/src/dataToEsm.ts +++ b/src/dataToEsm.ts @@ -3,6 +3,10 @@ import { DataToEsm } from './pluginutils'; export type Indent = string | null | undefined; +function stringify(obj: any): string { + return (JSON.stringify(obj) || 'undefined').replace(/[\u2028\u2029]/g, char => `\\u${('000' + char.charCodeAt(0).toString(16)).slice(-4)}`); +} + function serializeArray(arr: Array, indent: Indent, baseIndent: string): string { let output = '['; const separator = indent ? '\n' + baseIndent + indent : ''; @@ -19,7 +23,7 @@ function serializeObject(obj: { [key: string]: T }, indent: Indent, baseInden const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { const key = keys[i]; - const stringKey = makeLegalIdentifier(key) === key ? key : JSON.stringify(key); + const stringKey = makeLegalIdentifier(key) === key ? key : stringify(key); output += `${i > 0 ? ',' : ''}${separator}${stringKey}:${indent ? ' ' : ''}${serialize( obj[key], indent, @@ -31,13 +35,15 @@ function serializeObject(obj: { [key: string]: T }, indent: Indent, baseInden function serialize(obj: any, indent: Indent, baseIndent: string): string { if (obj === Infinity) return 'Infinity'; + if (obj === -Infinity) return '-Infinity'; + if (obj === 0 && 1/obj === -Infinity) return '-0'; if (obj instanceof Date) return 'new Date(' + obj.getTime() + ')'; if (obj instanceof RegExp) return obj.toString(); - if (typeof obj === 'number' && isNaN(obj)) return 'NaN'; + if (obj !== obj) return 'NaN'; if (Array.isArray(obj)) return serializeArray(obj, indent, baseIndent); if (obj === null) return 'null'; if (typeof obj === 'object') return serializeObject(obj, indent, baseIndent); - return JSON.stringify(obj); + return stringify(obj); } const dataToEsm: DataToEsm = function dataToEsm(data, options = {}) { @@ -50,9 +56,14 @@ const dataToEsm: DataToEsm = function dataToEsm(data, options = {}) { options.namedExports === false || typeof data !== 'object' || Array.isArray(data) || + data instanceof Date || + data instanceof RegExp || data === null - ) - return `export default${_}${serialize(data, options.compact ? null : t, '')};`; + ) { + const code = serialize(data, options.compact ? null : t, ''); + const __ = _ || (/^[{[\-\/]/.test(code) ? '' : ' '); + return `export default${__}${code};`; + } let namedExportCode = ''; const defaultExportRows = []; @@ -69,7 +80,7 @@ const dataToEsm: DataToEsm = function dataToEsm(data, options = {}) { )};${n}`; } else { defaultExportRows.push( - `${JSON.stringify(key)}: ${serialize(data[key], options.compact ? null : t, '')}` + `${stringify(key)}:${_}${serialize(data[key], options.compact ? null : t, '')}` ); } } diff --git a/src/makeLegalIdentifier.ts b/src/makeLegalIdentifier.ts index f2d8334..04df298 100644 --- a/src/makeLegalIdentifier.ts +++ b/src/makeLegalIdentifier.ts @@ -15,7 +15,7 @@ export const makeLegalIdentifier: MakeLegalIdentifier = function makeLegalIdenti str = `_${str}`; } - return str; + return str || '_'; }; export { makeLegalIdentifier as default }; diff --git a/test/dataToEsm.test.ts b/test/dataToEsm.test.ts index 1bd56fa..ff74cbf 100644 --- a/test/dataToEsm.test.ts +++ b/test/dataToEsm.test.ts @@ -15,8 +15,8 @@ describe('dataToEsm', function() { it('supports non-JSON data', function() { const date = new Date(); - expect(dataToEsm({ inf: Infinity, date, number: NaN, regexp: /.*/ })).toEqual( - 'export var inf = Infinity;\nexport var date = new Date(' + + expect(dataToEsm({ inf: -Infinity, date, number: NaN, regexp: /.*/ })).toEqual( + 'export var inf = -Infinity;\nexport var date = new Date(' + date.getTime() + ');\nexport var number = NaN;\nexport var regexp = /.*/;\nexport default {\n\tinf: inf,\n\tdate: date,\n\tnumber: number,\n\tregexp: regexp\n};\n' ); @@ -28,11 +28,11 @@ describe('dataToEsm', function() { ).toEqual('export var some="data";export var another="data";export default{some,another};'); expect( dataToEsm( - { some: { deep: { object: 'definition', here: 'here' } }, another: 'data' }, + { some: { deep: { object: 'definition', here: 'here' } }, else: { deep: { object: 'definition', here: 'here' } } }, { compact: true, objectShorthand: false } ) ).toEqual( - 'export var some={deep:{object:"definition",here:"here"}};export var another="data";export default{some:some,another:another};' + 'export var some={deep:{object:"definition",here:"here"}};export default{some:some,"else":{deep:{object:"definition",here:"here"}}};' ); }); @@ -67,7 +67,7 @@ describe('dataToEsm', function() { }); it('exports default only for null', function() { - expect(dataToEsm(null)).toEqual('export default null;'); + expect(dataToEsm(null, { compact: true })).toEqual('export default null;'); }); it('exports default only for primitive values', function() { @@ -79,4 +79,10 @@ describe('dataToEsm', function() { 'export var a = "x";\nexport default {\n\ta: a,\n' + '\t"": "y"\n};\n' ); }); + + it('avoid U+2029 U+2029 -0 be ignored by JSON.stringify, and avoid it return non-string (undefined) before replacing', function() { + expect(dataToEsm([-0, '\u2028\u2029', undefined, function() {}], { compact: true })).toEqual( + 'export default[-0,"\\u2028\\u2029",undefined,undefined];' + ); + }); }); diff --git a/test/makeLegalIdentifier.test.ts b/test/makeLegalIdentifier.test.ts index b8002b9..be741cb 100644 --- a/test/makeLegalIdentifier.test.ts +++ b/test/makeLegalIdentifier.test.ts @@ -12,4 +12,8 @@ describe('makeLegalIdentifier', function() { it('blacklists arguments (https://github.com/rollup/rollup/issues/871)', function() { expect(makeLegalIdentifier('arguments')).toEqual('_arguments'); }); + + it('empty', function() { + expect(makeLegalIdentifier('')).toEqual('_'); + }); });