Skip to content

Line wrapping inside Markdoc tags breaks parser when closing tag is on same line #17

@jlevy

Description

@jlevy

Summary

When Flowmark wraps long Markdoc/Markform tags across multiple lines, it can break Markdoc's parser if the closing tag ({% /tag %}) remains on the same line as the wrapped opening tag's closing delimiter (%}).

Environment

  • Flowmark version: v0.6.1.dev13+0a5781e
  • Markdoc version: @markdoc/markdoc (latest)

The Problem

Flowmark correctly identifies and preserves Markdoc tag delimiters ({% ... %}), but when wrapping long tags, the closing tag placement can trigger a Markdoc parser bug that corrupts the AST structure.

Input (single line - works correctly)

{% outer %}
{% tag1 attr1=1 attr2=2 %}{% /tag1 %}
{% tag2 %}{% /tag2 %}
{% /outer %}

AST result: tag1 and tag2 correctly at the same nesting level inside outer.

After Flowmark wrapping (broken)

{% outer %}
{% tag1 attr1=1
attr2=2 %}{% /tag1 %}
{% tag2 %}{% /tag2 %}
{% /outer %}

AST result: tag2 incorrectly nested deep inside tag1, phantom tags appear.

Workaround that works

{% outer %}
{% tag1 attr1=1
attr2=2 %}
{% /tag1 %}
{% tag2 %}{% /tag2 %}
{% /outer %}

When the closing tag is on its own line after a multi-line opening tag, parsing works correctly.

Root Cause

This is technically a Markdoc parser bug (the Markdoc spec explicitly allows multi-line tags), but it's triggered by a specific pattern that Flowmark produces.

The problematic pattern is:

{% tag attr=value
more_attrs %}{% /tag %}

Where %}{% /tag %} appears on the same line after a multi-line opening tag.

Suggested Fix

When Flowmark wraps a long Markdoc tag across multiple lines, it should place the closing tag on a new line:

Current behavior:

{% field kind="string" id="name" label="Name" required=true minLength=2
maxLength=50 placeholder="Enter your name" %}{% /field %}

Suggested behavior:

{% field kind="string" id="name" label="Name" required=true minLength=2
maxLength=50 placeholder="Enter your name" %}
{% /field %}

This maintains proper formatting while avoiding the Markdoc parser bug.

Reproduction

# Create test file
cat > test.md << 'TESTEOF'
{% form id="test" %}
{% group id="group1" %}
{% field kind="number" id="age" label="Age" role="user" required=true min=0 max=150 integer=true placeholder="25" %}{% /field %}
{% field kind="string" id="name" %}{% /field %}
{% /group %}
{% group id="group2" %}
{% field kind="url" id="website" %}{% /field %}
{% /group %}
{% /form %}
TESTEOF

# Format with flowmark
flowmark --auto test.md

# Parse with Markdoc and count groups (should be 2, but shows 1)
node --input-type=module -e "
import Markdoc from '@markdoc/markdoc';
import { readFileSync } from 'fs';
const ast = Markdoc.parse(readFileSync('test.md', 'utf-8'));
function countGroups(node) {
  let count = 0;
  if (node.type === 'tag' && node.tag === 'group') count++;
  for (const child of node.children || []) count += countGroups(child);
  return count;
}
console.log('Groups found:', countGroups(ast));
"

Impact

This affects any project using Markdoc-style tags (Markdoc, Markform, similar template systems) where tags have many attributes that exceed the line width limit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions