Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion packages/yaml/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ const defaults = {
extensions: ['.yaml', '.yml']
};

function serialize(data, ancestors = []) {
if (data === null || typeof data !== 'object') return toSource(data);
if (!Array.isArray(data)) {
const prototype = Object.getPrototypeOf(data);
if (prototype !== Object.prototype && prototype !== null) return toSource(data);
}
if (ancestors.includes(data)) return '{$circularReference:1}';
const parents = ancestors.concat([data]);
if (Array.isArray(data)) {
return `[${data.map((value) => serialize(value, parents)).join(',')}]`;
}
return `{${Object.keys(data)
.map((key) => `${JSON.stringify(key)}:${serialize(data[key], parents)}`)
.join(',')}}`;
}

export default function yaml(opts = {}) {
const options = Object.assign({}, defaults, opts);
const { documentMode, extensions } = options;
Expand Down Expand Up @@ -48,7 +64,7 @@ export default function yaml(opts = {}) {
}

const keys = Object.keys(data).filter((key) => key === makeLegalIdentifier(key));
const code = `var data = ${toSource(data)};\n\n`;
const code = `var data = ${serialize(data)};\n\n`;
const exports = ['export default data;']
.concat(keys.map((key) => `export var ${key} = data.${key};`))
.join('\n');
Expand Down
8 changes: 8 additions & 0 deletions packages/yaml/test/fixtures/numeric-keys/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'00': zeroes
'01': leading zero
'08': eight
'9007199254740993': large integer
nested:
'01': nested leading zero
array:
- '01': array leading zero
1 change: 1 addition & 0 deletions packages/yaml/test/fixtures/numeric-keys/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './config.yaml';
7 changes: 7 additions & 0 deletions packages/yaml/test/fixtures/serialized-values/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
timestamp: 2020-01-02T03:04:05Z
values: [.inf, -.inf, .nan, -0.0]
shared: &shared
'01': alias value
alias: *shared
loop: &loop
self: *loop
1 change: 1 addition & 0 deletions packages/yaml/test/fixtures/serialized-values/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, shared } from './config.yaml';
122 changes: 122 additions & 0 deletions packages/yaml/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,128 @@ test('generates named exports', async () => {
return testBundle(undefined, bundle);
});

test('preserves quoted numeric mapping keys in an ES module', async () => {
const bundle = await rollup.rollup({
input: 'fixtures/numeric-keys/main.js',
plugins: [yaml()]
});
const { output } = await bundle.generate({ format: 'es' });
expect(output[0].code).toContain('export');
const { error, module } = await testBundle(null, bundle);
expect(error).toBeUndefined();
expect(module.exports).toEqual({
'00': 'zeroes',
'01': 'leading zero',
'08': 'eight',
'9007199254740993': 'large integer',
nested: { '01': 'nested leading zero' },
array: [{ '01': 'array leading zero' }]
});
});

test('preserves YAML timestamps, non-finite numbers and aliases', async () => {
const bundle = await rollup.rollup({
input: 'fixtures/serialized-values/main.js',
plugins: [yaml()]
});
const { error, module } = await testBundle(null, bundle, {
options: { exports: 'named' }
});
expect(error).toBeUndefined();
const data = module.exports.default;
expect(data.timestamp).toEqual(new Date('2020-01-02T03:04:05.000Z'));
expect(data.values).toEqual([Infinity, -Infinity, NaN, -0]);
expect(data.shared).toEqual({ '01': 'alias value' });
expect(data.alias).toEqual(data.shared);
expect(module.exports.shared).toBe(data.shared);
expect(data.loop.self).toEqual({ $circularReference: 1 });
});

test('preserves special values returned by a transform', async () => {
const data = {
regexp: /example/gi,
map: new Map([['key', 'value']]),
set: new Set(['value']),
fn: function value() {
return 42;
}
};
const bundle = await rollup.rollup({
input: 'fixtures/numeric-keys/main.js',
plugins: [yaml({ transform: () => data })]
});
const { error, module } = await testBundle(null, bundle);
expect(error).toBeUndefined();
expect(module.exports.regexp).toEqual(data.regexp);
expect(module.exports.map).toEqual(data.map);
expect(module.exports.set).toEqual(data.set);
expect(module.exports.fn()).toBe(42);
});

test('preserves other object and array serialization returned by a transform', async () => {
const circular = {};
circular.self = circular;
const circularArray = [];
circularArray.push(circularArray);
const custom = {
toSource: function customSource() {
return 'custom';
}
};
const data = {
boxedString: Object('abc'),
boxedNumber: Object(4),
boxedBoolean: Object(false),
error: new Error('example'),
custom,
sparse: Array(2).concat([1]),
Comment thread
greptile-apps[bot] marked this conversation as resolved.
proto: JSON.parse('{"__proto__":{"example":true}}'),
circularArray,
map: new Map([['key', circular]])
};
const bundle = await rollup.rollup({
input: 'fixtures/numeric-keys/main.js',
plugins: [yaml({ transform: () => data })]
});
const { error, module } = await testBundle(null, bundle);
expect(error).toBeUndefined();
expect(module.exports.boxedString).toEqual({ 0: 'a', 1: 'b', 2: 'c' });
expect(module.exports.boxedNumber).toEqual({});
expect(module.exports.boxedBoolean).toEqual({});
expect(module.exports.error).toEqual({});
expect(module.exports.custom.toSource()).toBe('custom');
expect(module.exports.sparse).toEqual(data.sparse);
expect(Object.getPrototypeOf(module.exports.proto)).toEqual({ example: true });
expect(module.exports.circularArray).toEqual([{ $circularReference: 1 }]);
expect(module.exports.map.get('key')).toEqual({ self: { $circularReference: 1 } });
});

test('preserves existing serialization of arrays with trailing holes', async () => {
const data = {
allHoles: Array(2),
moreHoles: Array(3),
trailingHole: [1].concat(Array(1)),
trailingHoles: [1].concat(Array(2))
};
const bundle = await rollup.rollup({
input: 'fixtures/numeric-keys/main.js',
plugins: [yaml({ transform: () => data })]
});
const { error, module } = await testBundle(null, bundle);
expect(error).toBeUndefined();

// The existing tosource serializer drops one trailing hole.
for (const key of Object.keys(data)) {
const result = module.exports[key];
expect(result.length).toBe(data[key].length - 1);
for (let index = 0; index < result.length; index += 1) {
expect(index in result).toBe(index in data[key]);
}
}
expect(module.exports.trailingHole[0]).toBe(1);
expect(module.exports.trailingHoles[0]).toBe(1);
});

test('resolves extensionless imports in conjunction with nodeResolve plugin', async () => {
const bundle = await rollup.rollup({
input: 'fixtures/extensionless/main.js',
Expand Down