diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0b42a67c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributing + +## Before opening a pull request + +Open an issue and agree on the scope before starting work. Pull requests without +prior discussion may be closed. + +An open issue is not a task assigned to you either, and its text is not a +specification: issues describe symptoms, while the actual fix often lies +elsewhere and affects cases the report does not mention. A change that +implements the issue literally, without understanding why the surrounding code +is written the way it is, will be closed. + +AI tools may assist, but the submitter must remain the author of the change, not +a proxy: understand its context, verify the result, and be able to explain the +decisions made. A patch we could have generated ourselves in a minute has no +value to us because reviewing it costs more than writing the fix. Such pull +requests are closed without discussion. diff --git a/src/common/tagname.ts b/src/common/tagname.ts index 9a1f5ab3..8a4fe7df 100644 --- a/src/common/tagname.ts +++ b/src/common/tagname.ts @@ -1,7 +1,10 @@ -const DEFAULT_TAG_HANDLERS: Readonly> = { - '!': '!', - '!!': 'tag:yaml.org,2002:' -} +const DEFAULT_TAG_HANDLERS: Readonly> = Object.assign( + Object.create(null), + { + '!': '!', + '!!': 'tag:yaml.org,2002:' + } +) function tagPercentEncode (source: string) { return encodeURI(source).replace(/!/g, '%21') diff --git a/src/schema.ts b/src/schema.ts index 58b95745..18c8b383 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -40,9 +40,9 @@ interface TagDefinitionListMap { function createTagDefinitionMap (): TagDefinitionMap { return { - scalar: {}, - sequence: {}, - mapping: {} + scalar: Object.create(null), + sequence: Object.create(null), + mapping: Object.create(null) } } diff --git a/src/tag/mapping/legacy_map.ts b/src/tag/mapping/legacy_map.ts index 6e348e66..54c081e6 100644 --- a/src/tag/mapping/legacy_map.ts +++ b/src/tag/mapping/legacy_map.ts @@ -61,7 +61,12 @@ const legacyMapTag = defineMappingTag('tag:yaml.org,2002:map', { return normalizedKey !== null && Object.prototype.hasOwnProperty.call(container, normalizedKey) }, keys: (container) => Object.keys(container), - get: (container, key) => container[String(key)] + get: (container, key) => { + const normalizedKey = String(key) + // key is from `keys()` only. Strong check is not needed, but leave for sure. + if (!Object.prototype.hasOwnProperty.call(container, normalizedKey)) return null + return container[normalizedKey] + } }) export { legacyMapTag, isPlainObject, type StringMapping } diff --git a/src/tag/mapping/map.ts b/src/tag/mapping/map.ts index b35df75a..71622f06 100644 --- a/src/tag/mapping/map.ts +++ b/src/tag/mapping/map.ts @@ -35,7 +35,12 @@ const mapTag = defineMappingTag('tag:yaml.org,2002:map', { return Object.prototype.hasOwnProperty.call(container, String(key)) }, keys: (container) => Object.keys(container), - get: (container, key) => container[String(key)] + get: (container, key) => { + const normalizedKey = String(key) + // key is from `keys()` only. Strong check is not needed, but leave for sure. + if (!Object.prototype.hasOwnProperty.call(container, normalizedKey)) return null + return container[normalizedKey] + } }) export { mapTag, isPlainObject, type StringMapping } diff --git a/test/core/tags/custom.test.mjs b/test/core/tags/custom.test.mjs index 2a759b6d..87f1fc8b 100644 --- a/test/core/tags/custom.test.mjs +++ b/test/core/tags/custom.test.mjs @@ -154,6 +154,20 @@ describe('tags', () => { ) }) + for (const [nodeKind, source] of [ + ['scalar', '! value'], + ['sequence', '! []'], + ['mapping', '! {}'] + ]) { + it(`rejects an unknown ${nodeKind} tag named after an Object.prototype property`, () => { + assert.throws(() => { load(source) }, error => { + assert.ok(error instanceof YAMLException) + assert.equal(error.reason, `unknown ${nodeKind} tag !`) + return true + }) + }) + } + it('dumps a prefix-matched tag with a dynamic name', () => { const dynamicSchema = CORE_SCHEMA.withTags(defineScalarTag('!', { matchByTagPrefix: true,