From a3f116a135e550d75858f2789943c0fbdd0d96e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Wed, 17 Apr 2019 16:35:48 +0200 Subject: [PATCH] fix: handle edge case --- .../parseWithPointers.spec.ts.snap | 71 ++++++++++++++ src/__tests__/parseWithPointers.spec.ts | 94 ++++++++++++++++--- src/parseWithPointers.ts | 19 ++-- 3 files changed, 166 insertions(+), 18 deletions(-) diff --git a/src/__tests__/__snapshots__/parseWithPointers.spec.ts.snap b/src/__tests__/__snapshots__/parseWithPointers.spec.ts.snap index aeb7df0..c12e57f 100644 --- a/src/__tests__/__snapshots__/parseWithPointers.spec.ts.snap +++ b/src/__tests__/__snapshots__/parseWithPointers.spec.ts.snap @@ -1,5 +1,76 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`yaml parser dereferencing anchor refs insane edge case 1`] = ` +Array [ + Array [ + Array [ + undefined, + Object { + "test": Array [ + undefined, + ], + }, + Object { + "abc": Object { + "a": null, + "c": undefined, + "foo": 2, + "x": undefined, + }, + }, + ], + Object { + "test": Array [ + Array [ + undefined, + Object { + "test": Array [ + undefined, + ], + }, + Object { + "abc": Object { + "a": null, + "c": undefined, + "foo": 2, + "x": undefined, + }, + }, + ], + ], + }, + Object { + "abc": Object { + "a": null, + "c": Array [ + undefined, + Object { + "test": Array [ + undefined, + ], + }, + Object { + "abc": Object { + "a": null, + "c": undefined, + "foo": 2, + "x": undefined, + }, + }, + ], + "foo": 2, + "x": Object { + "a": null, + "c": undefined, + "foo": 2, + "x": undefined, + }, + }, + }, + ], +] +`; + exports[`yaml parser parse diverse 1`] = ` Object { "ast": Object { diff --git a/src/__tests__/parseWithPointers.spec.ts b/src/__tests__/parseWithPointers.spec.ts index 487f4d3..a547c9a 100644 --- a/src/__tests__/parseWithPointers.spec.ts +++ b/src/__tests__/parseWithPointers.spec.ts @@ -141,26 +141,98 @@ prop2: true }); }); - test('dereferences circular anchor refs', () => { - const result = parseWithPointers(`definitions: + describe('dereferencing anchor refs', () => { + test('ignore valid refs', () => { + const result = parseWithPointers(`austrian-cities: &austrian-cities + - Vienna + - Graz + - Linz + - Salzburg + +european-cities: + austria: *austrian-cities +`); + expect(result.data).toEqual({ + 'austrian-cities': ['Vienna', 'Graz', 'Linz', 'Salzburg'], + 'european-cities': { + austria: ['Vienna', 'Graz', 'Linz', 'Salzburg'], + }, + }); + }); + + test('support circular refs in mapping', () => { + const result = parseWithPointers(`definitions: model: &ref foo: name: *ref `); - expect(result.data).toEqual({ - definitions: { - model: { - foo: { - name: { - foo: { - name: undefined, + + expect(result.data).toEqual({ + definitions: { + model: { + foo: { + name: { + foo: { + name: undefined, + }, }, }, }, }, - }, + }); + + expect(() => JSON.stringify(result.data)).not.toThrow(); }); - expect(() => JSON.stringify(result.data)).not.toThrow(); + test('support circular in refs sequences', () => { + const result = parseWithPointers(`- &foo + - test: + - *foo +`); + expect(result.data).toEqual([ + [ + { + test: [[{ test: [undefined] }]], + }, + ], + ]); + + expect(() => JSON.stringify(result.data)).not.toThrow(); + }); + + test('support circular nested refs', () => { + const result = parseWithPointers(`a: &foo + - b: &bar + - true + - c: *bar + - *foo +`); + + expect(result.data).toEqual({ + a: [ + { + b: [true, { c: [true, { c: undefined }, undefined] }, [{ b: [true, { c: undefined }, undefined] }]], + }, + ], + }); + + expect(() => JSON.stringify(result.data)).not.toThrow(); + }); + + test('insane edge case', () => { + const result = parseWithPointers(`- &foo + - *foo + - test: + - *foo + - abc: &test + foo: 2 + a: + c: *foo + x: *test +`); + expect(result.data).toMatchSnapshot(); + + expect(() => JSON.stringify(result.data)).not.toThrow(); + }); }); }); diff --git a/src/parseWithPointers.ts b/src/parseWithPointers.ts index 8e82721..9e82c3e 100644 --- a/src/parseWithPointers.ts +++ b/src/parseWithPointers.ts @@ -50,7 +50,7 @@ const walk = (node: YAMLNode | null): unknown => { return 'valueObject' in node ? node.valueObject : node.value; case Kind.ANCHOR_REF: if (node.value !== undefined && isCircularAnchorRef(node as YAMLAnchorReference)) { - node.value = dereferenceAnchor(node.value, node); + node.value = dereferenceAnchor(node.value, (node as YAMLAnchorReference).referencesAnchor); } return walk(node.value); @@ -75,26 +75,31 @@ const isCircularAnchorRef = (anchorRef: YAMLAnchorReference) => { return false; }; -const dereferenceAnchor = (node: YAMLNode, parent: YAMLNode): YAMLNode | YAMLNode[] | void => { +const dereferenceAnchor = (node: YAMLNode, anchorId: string): YAMLNode | YAMLNode[] | void => { if (!node) return node; - if (node === parent) return; + if ('referencesAnchor' in node && (node as YAMLAnchorReference).referencesAnchor === anchorId) return; switch (node.kind) { case Kind.MAP: return { ...node, - mappings: (node as YamlMap).mappings.map(mapping => dereferenceAnchor(mapping, parent) as YAMLNode), + mappings: (node as YamlMap).mappings.map(mapping => dereferenceAnchor(mapping, anchorId) as YAMLNode), } as YamlMap; case Kind.SEQ: return { ...node, - items: (node as YAMLSequence).items.map(item => dereferenceAnchor(item, parent) as YAMLNode), + items: (node as YAMLSequence).items.map(item => dereferenceAnchor(item, anchorId) as YAMLNode), } as YAMLSequence; case Kind.MAPPING: - return { ...node, value: dereferenceAnchor(node.value, parent) }; + return { ...node, value: dereferenceAnchor(node.value, anchorId) }; case Kind.SCALAR: + return node; case Kind.ANCHOR_REF: - return { ...node }; + if (node.value !== undefined && isCircularAnchorRef(node as YAMLAnchorReference)) { + return; + } + + return node; default: return node; }