Skip to content
Merged
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: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 7 additions & 4 deletions src/common/tagname.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const DEFAULT_TAG_HANDLERS: Readonly<Record<string, string>> = {
'!': '!',
'!!': 'tag:yaml.org,2002:'
}
const DEFAULT_TAG_HANDLERS: Readonly<Record<string, string>> = Object.assign(
Object.create(null),
{
'!': '!',
'!!': 'tag:yaml.org,2002:'
}
)

function tagPercentEncode (source: string) {
return encodeURI(source).replace(/!/g, '%21')
Expand Down
6 changes: 3 additions & 3 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/tag/mapping/legacy_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
7 changes: 6 additions & 1 deletion src/tag/mapping/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
14 changes: 14 additions & 0 deletions test/core/tags/custom.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ describe('tags', () => {
)
})

for (const [nodeKind, source] of [
['scalar', '!<constructor> value'],
['sequence', '!<constructor> []'],
['mapping', '!<constructor> {}']
]) {
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 !<constructor>`)
return true
})
})
}

it('dumps a prefix-matched tag with a dynamic name', () => {
const dynamicSchema = CORE_SCHEMA.withTags(defineScalarTag('!', {
matchByTagPrefix: true,
Expand Down