Skip to content

Empty component props block throws expected a document, but the input is empty #319

Description

@adamdehaven

Environment

  • comark: 0.5.1
  • js-yaml: 5.2.2 (transitive)
  • Node: 24.x
  • OS: macOS (platform-independent)

Comark Version

0.5.1

Reproduction

import { parse } from 'comark'

// Empty props block (opening `---` immediately followed by closing `---`)
await parse('::page-section\n---\n---\n#title\nHello\n::')
// → throws: YAMLException: expected a document, but the input is empty

Blocks that are not empty parse correctly, at any nesting/indentation level:

await parse('::hero\n---\ntitle: x\n---\n#title\nHi\n::')            // OK
await parse('::a\n  ::b\n  ---\n  title: x\n  ---\n  ::\n::')        // OK (nested + indented)
await parse('::hero\n---\n   \n---\ncontent\n::')                    // throws (whitespace-only block)
  • Expected: an empty (or whitespace-only) props block yields a component with no prop (equivalent to omitting the block). No throw.
  • Actual: parse() rejects with expected a document, but the input is empty.

Description

A block component whose --- fenced YAML props block is empty (or contains only whitespace) crashes the parser. Comark passes the empty block to js-yaml, which throws YAMLException: expected a document, but the input is empty, and the error propagates out of parse(), failing the entire document rather than the one empty block.

@nuxtjs/mdc (Comark's predecessor) tolerated empty props blocks and simply produced a component with no props. Real-world authored content contains empty props blocks, so this is a migration blocker.

Additional context

Root cause

The block-props rule slices the YAML between the fences and calls parseYaml unconditionally:

packages/comark/src/plugins/syntax.tscomark_block_yaml (dist: dist/plugins/syntax.js:~245):

const yaml = state.src.slice(state.bMarks[startLine + 1], state.eMarks[lineEnd - 1])
const data = parseYaml(yaml)   // yaml === '' when the block is empty

packages/comark/src/internal/yaml.tsparseYaml (dist: dist/internal/yaml.js):

import { load, JSON_SCHEMA } from 'js-yaml'

export function parseYaml(content) {
  return load(content, { schema: JSON_SCHEMA })   // js-yaml throws on empty input
}

js-yaml's load('') (and whitespace-only input) throws expected a document, but the input is empty instead of returning undefined.

Proposed fix

Guard empty/whitespace input in parseYaml so it returns undefined (Comark already handles a falsy result via data || {} at the call site):

// packages/comark/src/internal/yaml.ts
export function parseYaml(content: string) {
  if (!content || content.trim() === '') {
    return undefined
  }
  return load(content, { schema: JSON_SCHEMA })
}

Alternatively (or additionally), short-circuit in comark_block_yaml when the sliced block is blank:

const yaml = state.src.slice(state.bMarks[startLine + 1], state.eMarks[lineEnd - 1])
const data = yaml.trim() === '' ? undefined : parseYaml(yaml)

Either change makes an empty props block behave like "no props", matching @nuxtjs/mdc.

Note: the fenced ```yaml [props] props variant likely shares the same code path and should be
covered by the same guard — worth adding a test for both forms.

Suggested tests

expect((await parse('::x\n---\n---\ncontent\n::')).nodes).toBeDefined()   // no throw
expect((await parse('::x\n---\n   \n---\ncontent\n::')).nodes).toBeDefined() // whitespace-only
expect((await parse('::x\n```yaml [props]\n```\ncontent\n::')).nodes).toBeDefined() // empty fenced props

Logs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions