Skip to content

elementAttributeNameCase: "html" is not applied to appendChild root properties or setProperty names (nested children convert) #192

Description

@gtritchie

What's the problem?

With elementAttributeNameCase: "html", the react→html attribute-name conversion is applied inconsistently depending on how a hast plugin sets the property:

  • Nested descendants of a subtree passed to ctx.appendChild are converted (strokeWidthstroke-width). ✅
  • The root node passed to ctx.appendChild is not converted — strokeWidth is emitted verbatim. ❌
  • ctx.setProperty names are not converted either. ❌

The same hast property name produces different JSX attribute names depending on its position, so plugin-added SVG presentation attributes leak invalid camelCase names into the rendered HTML (browsers ignore them). The root/setProperty path does apply a smaller special-case set (classNameclass, ariaHiddenaria-hidden), which makes the gap easy to miss until an SVG attribute is involved.

Repro (node repro.mjs):

import { mdxToJs, defineHastPlugin } from 'satteri';

const plugin = defineHastPlugin({
  name: 'repro',
  element: {
    filter: ['p'],
    visit(node, ctx) {
      ctx.appendChild(node, {
        type: 'element',
        tagName: 'svg',
        properties: { strokeWidth: 'ROOT' },
        children: [
          { type: 'element', tagName: 'path', properties: { strokeWidth: 'NESTED' }, children: [] },
        ],
      });
      ctx.setProperty(node, 'strokeWidth', 'SETPROP');
    },
  },
});

const { code } = await mdxToJs('hello', {
  hastPlugins: [plugin],
  elementAttributeNameCase: 'html',
});
console.log(code);

Output (satteri 0.9.5 — identical on 0.9.1):

    return _jsxs(_components.p, {
        strokeWidth: "SETPROP",
        children: ["hello", _jsx(_components.svg, {
            strokeWidth: "ROOT",
            children: _jsx(_components.path, { "stroke-width": "NESTED" })
        })]
    });

One property name, three spellings-in-flight, two survive to output incorrectly.

Downstream context: found while investigating why withastro/astro#17514 (which passes elementAttributeNameCase: "html" on the Sätteri MDX path, fixing withastro/astro#17512) appeared to work in Astro's test fixture but not in a real site. The fixture's attributes sat on a nested <path> (converted); the real-world plugin set them on the appended root node and via setProperty (leaked).

Environment Information

  • satteri npm v0.9.5 (reproduced identically on v0.9.1), prebuilt binary @bruits/satteri-darwin-arm64
  • macOS 26.6 (arm64)
  • Rust: n/a (prebuilt NAPI binary)
  • Node.js v24.18.0

What's the expected result?

All three positions emit "stroke-width". Property-name conversion should depend only on the configured elementAttributeNameCase, not on whether the property arrived on an appended subtree's root, a nested child, or via ctx.setProperty.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions