{"record":{"id":"55cd79204d5e05bb","repo":"remotion-dev/remotion","slug":"expected-a-printed-self-closing-jsx-element","errorCode":null,"errorMessage":"Expected a printed self-closing JSX element","messagePattern":"Expected a printed self-closing JSX element","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/studio-codemods/src/print-jsx.ts","lineNumber":321,"sourceCode":"\t\t[],\n\t);\n\tconst printed = printInsertedJsx({\n\t\telement: printableElement,\n\t\tinput,\n\t\toriginalAttributeSources,\n\t\tprettierConfigOverride: {\n\t\t\tbracketSpacing: getObjectCurlySpacing(input, prettierConfigOverride),\n\t\t\tsingleQuote:\n\t\t\t\tgetPreferredQuote(input, prettierConfigOverride) === 'single',\n\t\t\t...prettierConfigOverride,\n\t\t},\n\t});\n\tif (wasSelfClosing) {\n\t\treturn printed;\n\t}\n\n\tif (!printed.endsWith('/>')) {\n\t\tthrow new Error('Expected a printed self-closing JSX element');\n\t}\n\n\tconst slashIndex = printed.length - 2;\n\tconst lastLineStart = printed.lastIndexOf('\\n') + 1;\n\tconst beforeSlash = printed.slice(lastLineStart, slashIndex);\n\treturn beforeSlash.trim().length === 0\n\t\t? `${printed.slice(0, slashIndex)}>`\n\t\t: `${printed.slice(0, slashIndex).trimEnd()}>`;\n};\n","sourceCodeStart":303,"sourceCodeEnd":331,"githubUrl":"https://github.com/remotion-dev/remotion/blob/46a3a6bf13ca367376ff18bbb06dae2e87e0d068/packages/studio-codemods/src/print-jsx.ts#L303-L331","documentation":"In Remotion Studio codemods, printJsxOpeningElement reprints a JSX element and, when the source element was NOT self-closing, converts the printed output into a self-closing form by locating the trailing '/>'. If the printed output does not end with '/>', the transformation invariant is broken and the library throws instead of producing corrupted source. This is an internal formatting invariant, not a user-input validation.","triggerScenarios":"Calling printJsxOpeningElement (via a codemod that removes or rewrites children of a JSX element) when recast's generated output unexpectedly ends with children instead of a self-closing tag, e.g. after astTypes/printer customizations or when the AST has been mutated to still contain a children node while wasSelfClosing bookkeeping said otherwise.","commonSituations":"Developers writing a custom Studio codemod that mutates JSXElement children before calling this helper, or running against an unusual prettier/recast version whose JSX printer emits a different formatting (e.g. children on separate lines), so the printed string no longer ends with '/>'.","solutions":["Ensure you remove/clear the element's children (element.children = []) before calling printJsxOpeningElement so the printer emits a self-closing tag.","Check the codemod bookkeeping: wasSelfClosing must match the actual AST node; if the source element was self-closing, the function returns early without this check.","Verify no custom recast printer/prettier plugin rewrites JSX output; test with default printing options.","If you hit this after upgrading recast/prettier, pin known-working versions and reproduce with a minimal AST to file an issue."],"exampleFix":"// before: element still has children so print ends with '</Video>'\nconst printed = printJsxOpeningElement(path);\n// after\npath.node.children = [];\nconst printed = printJsxOpeningElement(path); // now ends with '/>'","handlingStrategy":"validation","validationCode":"const printed = recast.print(path.node).code;\nif (!printed.trimEnd().endsWith('/>')) {\n  throw new Error('Element still renders with children; clear children before printing');\n}","typeGuard":"const isSelfClosing = (node: t.JSXElement) => node.openingElement.selfClosing && node.children.length === 0;","tryCatchPattern":"try {\n  const printed = printJsxOpeningElement(path);\n} catch (err) {\n  if ((err as Error).message.includes('self-closing JSX')) {\n    path.node.children = [];\n  }\n}","preventionTips":["Clear node.children before requesting a self-closing print","Keep wasSelfClosing bookkeeping in sync with the actual AST","Test codemods against default recast/prettier printing settings","Pin recast and prettier versions in the codemod package"],"tags":["codemod","jsx","ast","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"46a3a6bf13ca367376ff18bbb06dae2e87e0d068","analyzedAt":"2026-09-18T00:12:20.256Z","contentChangedAt":"2026-09-18T00:12:20.256Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}