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
3 changes: 3 additions & 0 deletions src/ast/from_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ function eventsToAst (events: Event[], options: FromEventsOptions): Document[] {

case EVENT_POP: {
const frame = state.frames.pop()!
if (frame.kind === 'mapping' && frame.key) {
throw new Error('incomplete mapping pair in event stream')
}
if (frame.kind === 'document') {
state.documents.push(frame.doc)
} else {
Expand Down
5 changes: 5 additions & 0 deletions src/parser/constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ function constructFromEvents (events: Event[], options: ConstructorOptions): unk
case EVENT_POP: {
const frame = state.frames.pop()!

if (frame.kind === 'mapping' && frame.hasKey) {
state.position = frame.keyPosition
throwError(state, 'incomplete mapping pair in event stream')
}

if (frame.kind === 'document') {
state.documents.push(frame.value)
} else {
Expand Down
5 changes: 0 additions & 5 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1175,11 +1175,6 @@ function parseNode (
}
}

if (state.position === state.lineStart && testDocumentSeparator(state)) {
state.depth--
return false
}

if (indentStatus === 1) {
while (true) {
const ch = state.input.charCodeAt(state.position)
Expand Down
22 changes: 20 additions & 2 deletions src/tag/scalar/timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ const YAML_TIMESTAMP_REGEXP = new RegExp(
'(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' +
'(?::([0-9][0-9]))?))?$')

function makeUtcDate (
year: number,
month: number,
day: number,
hour = 0,
minute = 0,
second = 0,
fraction = 0
) {
const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction))

// Date.UTC() treats years 0..99 as 1900..1999. Restore the parsed YAML year
// before validating calendar normalization, e.g. reject 0001-02-29.
date.setUTCFullYear(year, month, day)

return date
}

function resolveYamlTimestamp (source: string) {
let match = YAML_DATE_REGEXP.exec(source)
if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(source)
Expand All @@ -26,7 +44,7 @@ function resolveYamlTimestamp (source: string) {

// Date-only form (`YYYY-MM-DD`) has no time captures.
if (!match[4]) {
const date = new Date(Date.UTC(year, month, day))
const date = makeUtcDate(year, month, day)
// Reject dates that JS would normalize, e.g. 2023-02-29 -> 2023-03-01.
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day) {
return NOT_RESOLVED
Expand All @@ -48,7 +66,7 @@ function resolveYamlTimestamp (source: string) {
fraction = +value
}

const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction))
const date = makeUtcDate(year, month, day, hour, minute, second, fraction)

// Reject invalid calendar dates before applying timezone offset.
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day) {
Expand Down
20 changes: 20 additions & 0 deletions test/core/ast/from_events.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import {
CORE_SCHEMA,
eventsToAst,
EVENT_SCALAR,
parseEvents
} from 'js-yaml'

describe('ast from_events', () => {
it('rejects a mapping event stream with an unpaired key', () => {
const source = 'key: value'
const events = parseEvents(source, {})
const valueIndex = events.findLastIndex(event => event.type === EVENT_SCALAR)

events.splice(valueIndex, 1)

assert.throws(() => eventsToAst(events, { source, schema: CORE_SCHEMA }))
})
})
20 changes: 20 additions & 0 deletions test/core/parser/constructor.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import {
constructFromEvents,
EVENT_SCALAR,
parseEvents,
YAMLException
} from 'js-yaml'

describe('constructor', () => {
it('rejects a mapping event stream with an unpaired key', () => {
const source = 'key: value'
const events = parseEvents(source, {})
const valueIndex = events.findLastIndex(event => event.type === EVENT_SCALAR)

events.splice(valueIndex, 1)

assert.throws(() => constructFromEvents(events, { source }), YAMLException)
})
})
25 changes: 25 additions & 0 deletions test/core/parser/parser.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { EVENT_SCALAR, getScalarValue, loadAll, parseEvents } from 'js-yaml'

describe('parser', () => {
it('keeps an implicit null mapping value before a document marker', () => {
const samples = [
['a:\n---\nx: 1\n', ['a', '', 'x', '1']],
['a:\n...\n', ['a', '']]
]

for (const [source, expected] of samples) {
const values = parseEvents(source, {})
.filter(event => event.type === EVENT_SCALAR)
.map(event => getScalarValue(source, event))

assert.deepEqual(values, expected)
}

assert.deepEqual(
loadAll('a:\n---\nx: 1\n'),
[{ a: null }, { x: 1 }]
)
})
})
5 changes: 5 additions & 0 deletions test/core/tags/timestamp.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ describe('tags', () => {
- 2001-12-14 21:59:43.10 -5 # space separated
- 2001-12-15 2:59:43.10 # no time zone (Z)
- 2002-12-14 # date (00:00:00Z)
- 0001-01-01
- 0050-06-15T12:30:00Z
- 2002-1-1 # not a date

# Other
Expand All @@ -26,6 +28,8 @@ describe('tags', () => {
new Date(Date.UTC(2001, 11, 15, 2, 59, 43, 100)),
new Date(Date.UTC(2001, 11, 15, 2, 59, 43, 100)),
new Date(Date.UTC(2002, 11, 14)),
new Date('0001-01-01T00:00:00.000Z'),
new Date('0050-06-15T12:30:00.000Z'),
'2002-1-1',

new Date(Date.UTC(2001, 11, 15, 3, 29, 43, 100)),
Expand All @@ -48,6 +52,7 @@ describe('tags', () => {
const invalid = [
'2023-99-99',
'2023-02-30',
'0001-02-29',
'2023-02-31 00:00:00',
'2023-01-01 24:00:00',
'2023-01-01 00:60:00',
Expand Down