fix(editor): keep // on JSX attribute lines and use {/* */} in MDX

Cmd+/ on an attribute line of a multi-line JSX tag wrapped it in {/* */},
which TypeScript rejects. Only lines that start a JSX child (text, the {
of a child escape, or the < of a nested element) take {/* */} now, and
attribute lines keep //.

.mdx files got <!-- -->, which MDX 2 and later fail to compile. They use
{/* */} now.

Co-authored-by: 3ae3ae <83870598+3ae3ae@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-05 03:54:11 +02:00
co-authored by 3ae3ae
parent 883c1bde67
commit f77692fefb
2 changed files with 34 additions and 6 deletions
@@ -9,6 +9,7 @@ test.each([
["source.css", "body {}", "/* body {} */"],
["source.html", "<p>text</p>", "<!-- <p>text</p> -->"],
["source.md", "text", "<!-- text -->"],
["source.mdx", "text", "{/* text */}"],
["source.ex", "value = 1", "# value = 1"],
["source.cs", "var value = 1;", "// var value = 1;"],
["source.nix", "1", "# 1"],
@@ -24,10 +25,18 @@ test.each([
const jsxView = [
"const view = (",
" <div>",
" <div",
' className="x"',
" onClick={() => go()}",
" >",
" text",
" {value}",
" <span />",
"{flag}",
"<br />",
" <Foo",
' a="1"',
" />",
" {items.map(() => {",
" const item = 1;",
" })}",
@@ -40,6 +49,11 @@ test.each([
["view.tsx", "text", "{/* text */}"],
["view.tsx", "{value}", "{/* {value} */}"],
["view.tsx", "<span />", "{/* <span /> */}"],
["view.tsx", "{flag}", "{/* {flag} */}"],
["view.tsx", "<br />", "{/* <br /> */}"],
["view.tsx", 'className="x"', '// className="x"'],
["view.tsx", "onClick={() => go()}", "// onClick={() => go()}"],
["view.tsx", 'a="1"', '// a="1"'],
["view.tsx", "const item = 1;", "// const item = 1;"],
])("toggles %s line %s as %s", (filename, line, commented) => {
const language = getLanguageForFile(filename);
+19 -5
View File
@@ -22,7 +22,7 @@ import { parser as rustParser } from "@lezer/rust";
import { parser as xmlParser } from "@lezer/xml";
import { parser as yamlParser } from "@lezer/yaml";
import { parser as elixirParser } from "lezer-elixir";
import type { Parser } from "@lezer/common";
import type { Parser, SyntaxNode } from "@lezer/common";
import type { LRParser } from "@lezer/lr";
import { csharpLanguage } from "./csharp/language.js";
import { astroParser } from "./astro/parser.js";
@@ -35,13 +35,27 @@ const hashComments = { commentTokens: { line: "#" } };
const cssComments = { commentTokens: { block: { open: "/*", close: "*/" } } };
const markupComments = { commentTokens: { block: { open: "<!--", close: "-->" } } };
const jsxChildComments = { commentTokens: { block: { open: "{/*", close: "*/}" } } };
// JSX children are text, so a `//` there renders on the page instead of commenting the line out.
// Attribute lines of a multi-line tag are not children and keep `//`.
const jsxComments: Sublanguage = {
// The `{` opening a JSX escape sits on a child line too, so judge it by its escape.
test: (node) => (node.name === "{" && node.parent ? node.parent : node).name.startsWith("JSX"),
facet: defineLanguageFacet({ commentTokens: { block: { open: "{/*", close: "*/}" } } }),
test: isJsxChild,
facet: defineLanguageFacet(jsxChildComments),
};
// A line starts on a child as text, as the `{` of an escape, or as the `<` of a nested element.
function isJsxChild(node: SyntaxNode): boolean {
return childAt(node)?.parent?.name === "JSXElement";
}
function childAt(node: SyntaxNode): SyntaxNode | null {
if (node.name === "JSXText") return node;
if (node.name === "{") return node.parent;
if (node.name === "JSXStartTag") return node.parent?.parent ?? null;
return null;
}
function language(
parser: LRParser | MarkdownParser,
languageData: Parameters<typeof defineLanguageFacet>[0] = {},
@@ -122,7 +136,7 @@ const languagesByExtension: Record<string, Language> = {
exs: language(elixirParser, hashComments),
// Markdown
md: language(markdownParser, markupComments),
mdx: language(markdownParser, markupComments),
mdx: language(markdownParser, jsxChildComments),
};
export function getLanguageForFile(filename: string): Language | null {