From e8cf6f6aa335c6c63d844b470240c9be661baa7f Mon Sep 17 00:00:00 2001 From: Alex Kocharin Date: Tue, 29 Dec 2020 13:04:52 +0300 Subject: [PATCH] Fix error with anchor not being assigned to an empty node fix https://github.com/nodeca/js-yaml/issues/301 --- CHANGELOG.md | 1 + lib/loader.js | 93 +++++++++++++++++++++++---------------------- test/issues/0301.js | 28 ++++++++++++++ 3 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 test/issues/0301.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c7c8e0..f44da6b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed `bower.json`. - Tags are now url-decoded in `load()` and url-encoded in `dump()` (previously usage of custom non-ascii tags may have led to invalid YAML that can't be parsed). +- Anchors now work correctly with empty nodes, #301. ## [3.14.1] - 2020-12-07 diff --git a/lib/loader.js b/lib/loader.js index 712ffff2..f5adab25 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -1448,61 +1448,64 @@ function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact } } - if (state.tag !== null && state.tag !== '!') { - if (state.tag === '?') { - // Implicit resolving is not allowed for non-scalar types, and '?' - // non-specific tag is only automatically assigned to plain scalars. - // - // We only need to check kind conformity in case user explicitly assigns '?' - // tag, for example like this: "! [0]" - // - if (state.result !== null && state.kind !== 'scalar') { - throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); - } + if (state.tag === null) { + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } - for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { - type = state.implicitTypes[typeIndex]; + } else if (state.tag === '?') { + // Implicit resolving is not allowed for non-scalar types, and '?' + // non-specific tag is only automatically assigned to plain scalars. + // + // We only need to check kind conformity in case user explicitly assigns '?' + // tag, for example like this: "! [0]" + // + if (state.result !== null && state.kind !== 'scalar') { + throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); + } - if (type.resolve(state.result)) { // `state.result` updated in resolver if matched - state.result = type.construct(state.result); - state.tag = type.tag; - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } - break; + for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { + type = state.implicitTypes[typeIndex]; + + if (type.resolve(state.result)) { // `state.result` updated in resolver if matched + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; } + break; } + } + } else if (state.tag !== '!') { + if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) { + type = state.typeMap[state.kind || 'fallback'][state.tag]; } else { - if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) { - type = state.typeMap[state.kind || 'fallback'][state.tag]; - } else { - // looking for multi type - type = null; - typeList = state.typeMap.multi[state.kind || 'fallback']; - - for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { - if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { - type = typeList[typeIndex]; - break; - } + // looking for multi type + type = null; + typeList = state.typeMap.multi[state.kind || 'fallback']; + + for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { + if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { + type = typeList[typeIndex]; + break; } } + } - if (!type) { - throwError(state, 'unknown tag !<' + state.tag + '>'); - } + if (!type) { + throwError(state, 'unknown tag !<' + state.tag + '>'); + } - if (state.result !== null && type.kind !== state.kind) { - throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); - } + if (state.result !== null && type.kind !== state.kind) { + throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); + } - if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched - throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); - } else { - state.result = type.construct(state.result, state.tag); - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } + if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched + throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); + } else { + state.result = type.construct(state.result, state.tag); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; } } } diff --git a/test/issues/0301.js b/test/issues/0301.js new file mode 100644 index 00000000..25226d52 --- /dev/null +++ b/test/issues/0301.js @@ -0,0 +1,28 @@ +'use strict'; + + +const assert = require('assert'); +const yaml = require('../../'); + + +it('should assign anchor to an empty node', function () { + assert.deepStrictEqual( + yaml.load('foo: &a\nbar: *a\n'), + { foo: null, bar: null } + ); + + assert.deepStrictEqual( + yaml.load('{ foo: &a, bar: *a }'), + { foo: null, bar: null } + ); + + assert.deepStrictEqual( + yaml.load('- &a\n- *a\n'), + [ null, null ] + ); + + assert.deepStrictEqual( + yaml.load('[ &a, *a ]'), + [ null, null ] + ); +});